How to record screen in android using MediaProjectionManager

Thursday, February 16, 2017 Unknown 1 Comments


Starting from the Android lollipop there is a new api called MediaProjectionManager for recording mobile device screen video.
          In this session we will learn how it works in android.
First we get the MediaProjectionManager using getSystemService()(Available on any context)which provides the interface for Video recording permission intent and handling this intent creates MediaProjection instance. startActivityForResult() launches the permission intent and handled in onActivityResult().

First get the MediaProjetionManager instance.
private MediaProjectionManager mediaProjectionManager;
mediaProjectionManager = getSystemService(
    android.content.Context.MEDIA_PROJECTION_SERVICE);
Create the recording permission intent and show to the user
private static final int REQUEST_CODE_CAPTURE = 1000;
Intent recordingPermissionIntent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(recordingPermissionIntent, REQUEST_CODE_CAPTURE);
 This intent will be handled in Activity's onActivityResult() method
private MediaProjection mediaProjection;

public void onActivityResult(int requestCode, int resultCode, Intent intent) {

        if (resultCode == RESULT_OK) {
            mMediaProjection = mediaProjectionManager.getMediaProjection(resultCode, intent);

        } else {
            // user did not grant permissions
        }

}
Once the MediaProjection instance is available, We can create the virtual display and tell android on what surface you want to copy or record. First initialize the video encoder.
 
private static final String VIDEO_MIME_TYPE = "video/avc";
private static final int VIDEO_WIDTH = 1280;
private static final int VIDEO_HEIGHT = 720;
private Surface inputSurface;
private MediaCodec videoEncoder;
    MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(VIDEO_MIME_TYPE, VIDEO_WIDTH, VIDEO_HEIGHT);
        int frameRate = 30; // 30 fps

        // Set some required properties. The media codec may fail if these aren't defined.
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000000); // 6Mbps
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
        mediaFormat.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate);
        mediaFormat.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);
        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 1 seconds between I-frames

        // Create a MediaCodec encoder and configure it. Get a Surface we can use for recording into.
        try {
            videoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE);
            videoEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            inputSurface = videoEncoder.createInputSurface();
            videoEncoder.start();
        } catch (IOException e) {
               e.printStackTrace();
            //RELEASE ENCODERS
        }
Once the encoder is set up MediaMuxer will be required which converts the inputs into the outputs. We can have multiple inputs (audio from mic, video from camera)in this case only one input, so, this is simple. Create a virtual display representing the dimensions of the screen specified and outputting the screen to the surface of the encoder we created.
private MediaMuxer mMuxer;
    try {
            mMuxer = new MediaMuxer("/sdcard/video.mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        } catch (IOException ioe) {
            throw new RuntimeException("MediaMuxer creation failed", ioe);
        }

        // Get the display size and density.
        DisplayMetrics metrics = getResources().getDisplayMetrics();
        int screenWidth = metrics.widthPixels;
        int screenHeight = metrics.heightPixels;
        int screenDensity = metrics.densityDpi;

        // Start the video input.
        mediaProjection.createVirtualDisplay("Recording Display", screenWidth,
                screenHeight, screenDensity, 0 /* flags */, inputSurface,
                null /* callback */, null /* handler */);
 Now it's time to drain the encoder. It fetches an output buffer from the encoder and writes all the bytes to the muxer. This means that all the recently recorded video will be written to the file produced by the muxer. We use a handler with a ten millisecond delay to  call repeatedly until the resources are released.
private boolean mMuxerStarted = false;
private int mTrackIndex = -1;    
private final Handler mDrainHandler = new Handler(Looper.getMainLooper());
    private Runnable mDrainEncoderRunnable = new Runnable() {
        @Override
        public void run() {
            // DRAIN ENCODER
        }
    };
Drain encoder
    mDrainHandler.removeCallbacks(mDrainEncoderRunnable);
    while (true) {
        int bufferIndex = videoEncoder.dequeueOutputBuffer(videoBufferInfo, 0);

        if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
            // nothing available yet
            break;
        } else if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // should happen before receiving buffers, and should only happen once
            if (mTrackIndex >= 0) {
                throw new RuntimeException("format changed twice");
            }
            mTrackIndex = mMuxer.addTrack(videoEncoder.getOutputFormat());
            if (!mMuxerStarted && mTrackIndex >= 0) {
                mMuxer.start();
                mMuxerStarted = true;
            }
        } else if (bufferIndex < 0) {
            // not sure what's going on, ignore it
        } else {
            ByteBuffer encodedData = videoEncoder.getOutputBuffer(bufferIndex);
            if (encodedData == null) {
                throw new RuntimeException("couldn't fetch buffer at index " + bufferIndex);
            }

            if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                videoBufferInfo.size = 0;
            }

            if (videoBufferInfo.size != 0) {
                if (mMuxerStarted) {
                    encodedData.position(videoBufferInfo.offset);
                    encodedData.limit(videoBufferInfo.offset + videoBufferInfo.size);
                    mMuxer.writeSampleData(mTrackIndex, encodedData, videoBufferInfo);
                } else {
                    // muxer not started
                }
            }

            videoEncoder.releaseOutputBuffer(bufferIndex, false);

            if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                break;
            }
        }
    }

    mDrainHandler.postDelayed(mDrainEncoderRunnable, 10);
When recording is finished release the encoder and other resources
mDrainHandler.removeCallbacks(mDrainEncoderRunnable);
    if (mMuxer != null) {
        if (mMuxerStarted) {
            mMuxer.stop();
        }
        mMuxer.release();
        mMuxer = null;
        mMuxerStarted = false;
    }
    if (videoEncoder != null) {
        videoEncoder.stop();
        videoEncoder.release();
        videoEncoder = null;
    }
    if (inputSurface != null) {
        inputSurface.release();
        inputSurface = null;
    }
    if (mediaProjection != null) {
        mediaProjection.stop();
        mediaProjection = null;
    }
    videoBufferInfo = null;
    mDrainEncoderRunnable = null;
    mTrackIndex = -1;
Never forget to give READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions.

1 comment:

  1. Hello, in your case only only video screen is working, not audio

    ReplyDelete