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.