【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;
	    }
	}

发布了442 篇原创文章 · 获赞 45 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/u013718730/article/details/104396847