【Android音視頻開發】【008】通過安卓系統服務進行屏幕截圖和錄像

安卓提供了一個屏幕投影服務(Media Projection Service),可用於將屏幕影像投影到虛擬顯示設備(Surface)
利用這個服務,我們可以對屏幕進行截圖和錄像

屏幕截圖


    final private static int code = 10086;

    //截圖
    MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    startActivityForResult(manager.createScreenCaptureIntent(), code);

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != code || resultCode != Activity.RESULT_OK) return;

        //獲取屏幕投影服務
        MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        MediaProjection projection = manager.getMediaProjection(resultCode, data);

		//獲取屏幕大小
        int w = getWindow().getDecorView().getWidth();
        int h = getWindow().getDecorView().getHeight();
        int dpi = Device.getScreenDpi(ctx);

        //將屏幕投影到虛擬顯示設備(ImageReader)
        //ImageReader可以保存多個幀的數據,由於我們截圖只需要一幀,所以最大圖片數量設置爲1
        ImageReader imageReader = ImageReader.newInstance(w, h, PixelFormat.RGBA_8888, 1);
        VirtualDisplay display = projection.createVirtualDisplay(
                "ScreenCapture",
                w,
                h,
                dpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader.getSurface(),
                null,
                null
        );

        //獲取圖像回調
        imageReader.setOnImageAvailableListener(reader -> {
            //獲取圖像數據
            Image image = imageReader.acquireLatestImage();
            Image.Plane[] planes = image.getPlanes();
            ByteBuffer buffer = planes[0].getBuffer();
            //Image轉Bitmap,並進行修剪
            //ImageReader爲了保持字節對齊,字節集中會有空的字節數據
            int pixelStride = planes[0].getPixelStride();
            int rowStride = planes[0].getRowStride();
            int rowPadding = rowStride - pixelStride * w;
            Bitmap bitmap = Bitmap.createBitmap(w + rowPadding / pixelStride, h, Bitmap.Config.ARGB_8888);
            bitmap.copyPixelsFromBuffer(buffer);
            bitmap = Bitmap.createBitmap(bitmap, rowPadding / pixelStride / 2, 0, w, h);
            image.close();
            //寫入文件
            Bitmaps.writeBitmapToFile(bitmap, "sdcard/001/1.png", 100);
            //停止投影,釋放資源
            //如果不停止,會一直向ImageReader寫入圖片
            display.release();
            projection.stop();
            //消息提示
            TipBox.tip("截圖成功");
        }, null);
    }

屏幕錄像

屏幕錄像和屏幕截圖的原理是一致的,但是視頻需要通過MediaCodec編碼爲H264,再通過MediaMuxer封裝爲MP4
代碼中的VideoPlayer是我自己的界面控件,大家可以無視,另外代碼中用到了一些簡化工具,大家需要適當調整才能使用,並不影響核心代碼


	import android.app.Activity;
	import android.content.Context;
	import android.content.Intent;
	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.media.projection.MediaProjectionManager;
	import android.view.Surface;
	import android.view.View;
	import android.view.WindowManager;
	
	import com.easing.commons.android.app.CommonActivity;
	import com.easing.commons.android.io.Files;
	import com.easing.commons.android.manager.Device;
	import com.easing.commons.android.thread.Threads;
	import com.easing.commons.android.ui.dialog.TipBox;
	
	import java.nio.ByteBuffer;
	
	import butterknife.BindView;
	import butterknife.ButterKnife;
	import io.vov.vitamio.core.VideoPlayer;
	import lombok.SneakyThrows;
	
	@SuppressWarnings("all")
	public class LoginActivity extends CommonActivity<LoginActivity> {
	
	    @BindView(R.id.bt1)
	    View bt1;
	    @BindView(R.id.bt2)
	    View bt2;
	    @BindView(R.id.bt3)
	    View bt3;
	
	    @BindView(R.id.v)
	    VideoPlayer view;
	
	    final private static int code = 10086;
	    final private static String path = "sdcard/001/1.mp4";
	
	    boolean recording = false;
	    boolean working = false;
	
	    int w;
	    int h;
	    int dpi;
	
	    MediaProjectionManager manager;
	    MediaProjection projection;
	
	    Surface surface;
	    VirtualDisplay display;
	
	    MediaCodec mediaCodec;
	    MediaMuxer mediaMuxer;
	
	    MediaCodec.BufferInfo bufferInfo;
	    Integer videoTrackIndex;
	
	    protected void create() {
	        setContentView(R.layout.activity_main);
	        ButterKnife.bind(this, ctx);
	        //申請存儲卡權限
	        requestAllPermissionWithCallback();
	    }
	
	    @Override
	    protected void onPermissionOk() {
	        view.showControlPane(false);
	        view.url("http://47.107.180.190:8657/07201811130443:1:1/stream");
	
	        bt1.setOnClickListener(v -> {
	        });
	
	        bt2.setOnClickListener(v -> {
	            if (working)
	                stopRecord();
	            else
	                startRecord();
	        });
	
	        bt3.setOnClickListener(v -> {
	            APP.ctx.finishProcess();
	        });
	    }
	
	    @SneakyThrows
	    private void startRecord() {
	        //獲取屏幕信息
	        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
	        w = windowManager.getDefaultDisplay().getWidth();
	        h = windowManager.getDefaultDisplay().getHeight();
	        dpi = Device.getScreenDpi(ctx);
	
	        //配置編碼器
	        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", w, h);
	        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
	        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
	        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
	        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
	        mediaCodec = MediaCodec.createEncoderByType("video/avc");
	        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
	        surface = mediaCodec.createInputSurface();
	        mediaCodec.start();
	
	        //開始屏幕投影服務
	        manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
	        startActivityForResult(manager.createScreenCaptureIntent(), code);
	
	        TipBox.tip("開始錄製");
	        working = true;
	    }
	
	    private void stopRecord() {
	        //注意這裏是將recording置爲false,而不是working置爲false
	        //等資源釋放完畢時,纔會自動將working置爲false
	        //這樣當我們快速練連點錄製按鈕時,就不會出現資源尚未釋放,又開始重新錄製的問題
	        //這就是我們爲什麼引入recording和working兩個控制變量的原因
	        //recording=false表示我們命令工作停止,working=false表示實際工作已經完全停止
	        //大家在處理耗時指令和狀態切換任務時,注意利用這個技巧
	        recording = false;
	    }
	
	    @Override
	    @SneakyThrows
	    public void onActivityResult(int requestCode, int resultCode, Intent data) {
	        if (requestCode != code || resultCode != Activity.RESULT_OK) return;
	
	        //獲取屏幕投影服務
	        projection = manager.getMediaProjection(resultCode, data);
	
	        //投影到編碼器的Surface
	        display = projection.createVirtualDisplay(
	                "record_screen",
	                w,
	                h,
	                dpi,
	                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
	                surface,
	                null,
	                null
	        );
	
	        //開啓錄製線程
	        Threads.post(() -> {
	            Files.createFile(path);
	            mediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
	            bufferInfo = new MediaCodec.BufferInfo();
	            recording = true;
	            while (recording) {
	                int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
	                //輸出格式改變
	                if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
	                    MediaFormat newFormat = mediaCodec.getOutputFormat();
	                    videoTrackIndex = mediaMuxer.addTrack(newFormat);
	                    mediaMuxer.start();
	                }
	                //暫無數據,請等待
	                if (index == MediaCodec.INFO_TRY_AGAIN_LATER)
	                    Threads.sleep(10);
	                //得到編碼數據
	                if (index >= 0) {
	                    ByteBuffer encodedData = mediaCodec.getOutputBuffer(index);
	                    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) bufferInfo.size = 0;
	                    if (bufferInfo.size == 0) encodedData = null;
	                    if (encodedData != null) {
	                        encodedData.position(bufferInfo.offset);
	                        encodedData.limit(bufferInfo.offset + bufferInfo.size);
	                        mediaMuxer.writeSampleData(videoTrackIndex, encodedData, bufferInfo);
	                    }
	                    mediaCodec.releaseOutputBuffer(index, false);
	                }
	            }
	            release();
	        });
	    }
	
	    private void release() {
	        display.release();
	        display = null;
	
	        projection.stop();
	        projection = null;
	
	        mediaMuxer.stop();
	        mediaMuxer.release();
	        mediaMuxer = null;
	
	        mediaCodec.stop();
	        mediaCodec.release();
	        mediaCodec = null;
	
	        videoTrackIndex = null;
	        bufferInfo = null;
	
	        TipBox.tip("錄製完成");
	        working = false;
	    }
	}

發佈了429 篇原創文章 · 獲贊 43 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章