關於手機錄屏功能小記(2)
阿新 • • 發佈:2018-12-21
本小記繼續上一篇的程式碼
主要實現功能為:對編碼器編譯壓縮後輸出的視訊流用MediaMuxer封裝到MP4容器。
//採用執行緒進行錄屏的耗時操作 public class VideoThread extends Thread { private Context mContext; private static final String MIME_TYPE = "video/avc"; private VirtualDisplay virtualDisplay; private MediaProjection projection; private AtomicBoolean mQuit = new AtomicBoolean(false); private int videoWidth; private int videoHeight; private Handler handler = null; //建構函式,傳入上下文和螢幕大小 public VideoThread(Context context, int width, int height){ mContext = context; videoWidth = width; videoHeight = height; } public final void quit(){ mQuit.set(true); } @Override public void run(){ Surface surface = null; MediaMuxer mediaMuxer = null; //MediaMuxer的使用要按照Constructor -> addTrack -> start -> writeSampleData -> stop 的順序 MediaCodec mediaCodec = null; //獲取檔案儲存路徑 long timeMillis = System.currentTimeMillis(); Date date = new Date(timeMillis); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); String path = "ScreenShot" + simpleDateFormat.format(date) + ".mp4"; File file = new File(Environment.getExternalStorageDirectory(), path); File parentFile = file.getParentFile(); if (parentFile == null || !parentFile.exists()) { parentFile.mkdir(); } //初始化編碼器、建立MediaFormat設定Media格式 MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, videoWidth, videoHeight); format.setInteger(MediaFormat.KEY_FRAME_RATE, 32); //幀數 format.setInteger(MediaFormat.KEY_BIT_RATE, 2500000); //碼流 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //關鍵幀 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); //顏色格式 format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 0); //音訊的頻道數(單聲道或者雙聲道) format.setInteger(MediaFormat.KEY_CAPTURE_RATE, 32); //捕獲率 (當CAPTURE_RATE和FRAME_RATE不一樣時,視訊播放會加快或減慢) format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 15); try { try {//建立編碼器(MediaCodec用於將音視訊進行壓縮編碼,可以對Surface內容進行編碼) mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); } catch (Exception e) { e.printStackTrace(); } //配置MedidaCodec解碼器、獲取surface作為輸入(必須在MediaCodec解碼器configure之後,start之前) mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = mediaCodec.createInputSurface();//請求一個surface用於編碼器的輸入,而不是常用的inputbuffer輸入 mediaCodec.start(); try {//MediaMuxer來封裝編碼後的視訊流和音訊流到mp4容器、通過Muxer指定視訊檔案輸出路徑和檔案格式 mediaMuxer = new MediaMuxer(file.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (Exception e) { e.printStackTrace(); mediaMuxer = null; } if (mediaMuxer == null) { return; } /*建立虛擬螢幕以捕獲螢幕內容 * * MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); * startActivityForResult(manager.createScreenCaptureIntent(), 1); //返回一個必須傳遞給startActivityForResult()的意圖,以便開始螢幕捕獲。(該活動將提示使用者是否允許螢幕捕獲) * * @Override * public void onActivityResult(int requestCode, int resultCode, Intent data){ * if (requestCode == 1) { * if (resultCode != Activity.RESULT_OK) { * return; * } * mResultCode = resultCode; * mResultData = data; * } * } * * public static MediaProjection getProjection(){ * if (projection == null) { * projection = manager.getMediaProjection(mResultCode, mResultData); //成功獲取螢幕捕獲請求後,檢索MediaProjection。 * } * return projection; * } * */ projection = MainActivity.getProjection(); virtualDisplay = projection.createVirtualDisplay("display", videoWidth, videoHeight, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); //獲取每一個輸出資料buffer的元資料資訊,例如偏差,在相關解碼器中有效的資料大小 int videoTrackIndex = -1; while (!mQuit.get()) { int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000); //嘗試獲取輸出資料的資訊,關於bytebuffer的資訊將封裝在bufferinfo裡面,返回該bytebuffer在佇列中的位置 //MediaCodec在一開始呼叫dequeueOutputBuffer()時會返回一次INFO_OUTPUT_FORMAT_CHANGED訊息 switch (index) { case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: MediaFormat newFormat = mediaCodec.getOutputFormat(); //在dequeueOutputBuffer 返回 INFO_OUTPUT_FORMAT_CHANGED資訊後呼叫,可以檢視當前媒體格式資訊 videoTrackIndex = mediaMuxer.addTrack(newFormat); //添加當前媒體格式的索引 mediaMuxer.start(); break; case MediaCodec.INFO_TRY_AGAIN_LATER: //如果等待timeoutUs時間還沒響應則跳過,返回TRY_AGAIN_LATER try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } break; default: ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(index); //根據Index獲取編碼成功的bytebuffer if (byteBuffer != null) { byteBuffer.position(bufferInfo.offset); //設定buffer位置,下一個要被讀或寫的元素的索引,每次讀寫緩衝區資料時都會改變改值,為下次讀寫作準備 byteBuffer.limit(bufferInfo.offset + bufferInfo.size); //limit表示緩衝區的當前終點,不能對緩衝區超過極限的位置進行讀寫操作。且極限是可以修改的 mediaMuxer.writeSampleData(videoTrackIndex, byteBuffer, bufferInfo); //將當前解碼器的buffer資料和buffer資料對應的bufferInfo寫入Muxer } mediaCodec.releaseOutputBuffer(index, false); //釋放剛剛從Codec取出資料的bytebuffer,供Codec繼續放資料。 break; } } } finally { //錄製完成後,釋放各項資源 try { if (mediaMuxer != null) { mediaMuxer.stop(); mediaMuxer.release(); } }catch (Exception e){ e.printStackTrace(); } try { if (surface != null) { surface.release(); } }catch (Exception e){ e.printStackTrace(); } try { if (mediaCodec != null) { mediaCodec.release(); } }catch (Exception e){ e.printStackTrace(); } File dstFile = new File(file.getAbsolutePath()); dstFile.setReadable(true, false); //第一個true表示是否可讀,第二個引數(true : 對所有人有效 false:對檔案擁有者有效) } if (file.exists()) { /*用於執行緒中的訊息處理,因為非主執行緒沒有預設建立Looper物件,需要呼叫該方法啟動Looper * 通過Handler物件來與Looper進行互動的。Handler可看做是Looper的一個介面,用來向指定的Looper傳送訊息及定義處理方法。 * Looper.loop(); 讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。 * 注意:寫在Looper.loop()之後的程式碼不會被執行,這個函式內部應該是一個迴圈,當呼叫mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。 */ Looper.prepare(); Message message = new Message(); android.os.Handler handler = new android.os.Handler(){ @Override public void handleMessage(Message msg){ super.handleMessage(msg); switch (msg.what) { case 1 : Toast.makeText(mContext, "File is exists", Toast.LENGTH_SHORT).show();break; } } }; message.what = 1; handler.sendMessage(message); Looper.loop(); } } }