Vijai Vijai - 1 month ago 25
Android Question

Screen record is black using mediaprojection

I'm trying to record screen using mediaProjection API but I always seem to get only a black video (the entire length of video shows black) and I have gone clueless on why. I call startRecording() after initializing the MediaProjectionManager and then call releaseEncoders() to stop the recording. Here is my code:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_record);

mMediaProjectionManager = (MediaProjectionManager) getSystemService(
android.content.Context.MEDIA_PROJECTION_SERVICE);

ToggleButton rec = (ToggleButton) findViewById(R.id.tggl);

String[] perms = {"android.permission.WRITE_EXTERNAL_STORAGE"};

if (!isStorageWritable()) {
// We don't have permission so prompt the user
requestPermissions(
perms,
REQUEST_EXTERNAL_STORAGE
);
}
rec.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (((ToggleButton) view).isChecked()) {
if (isStorageWritable()) {
Intent permissionIntent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(permissionIntent, REQUEST_CODE_CAPTURE_PERM);
}
} else
releaseEncoders();
}
});
}

private boolean isStorageWritable(){
int permission = ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE");
if (permission == PackageManager.PERMISSION_GRANTED)
return true;
else
return false;
}

private Runnable mDrainEncoderRunnable = new Runnable() {
@Override
public void run() {
drainEncoder();
}
};

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (REQUEST_CODE_CAPTURE_PERM == requestCode) {
if (resultCode == RESULT_OK) {
mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
startRecording(); // defined below
} else {
// user did not grant permissions
}
}
}
private void startRecording() {
DisplayManager dm = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
if (defaultDisplay == null) {
throw new RuntimeException("No display found.");
}
prepareVideoEncoder();

try {
mMuxer = new MediaMuxer(Environment.getExternalStorageDirectory() + "/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.
mMediaProjection.createVirtualDisplay("Recording Display", screenWidth,
screenHeight, screenDensity, 0 /* flags */, mInputSurface,
null /* callback */, null /* handler */);

// Start the encoders
drainEncoder();
}

private void prepareVideoEncoder() {
mVideoBufferInfo = new MediaCodec.BufferInfo();
MediaFormat format = 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.
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000); // 6Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate);
format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.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 {
mVideoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE);
mVideoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface = mVideoEncoder.createInputSurface();
mVideoEncoder.start();
} catch (IOException e) {
releaseEncoders();
}
}

private boolean drainEncoder() {
mDrainHandler.removeCallbacks(mDrainEncoderRunnable);
while (true) {
int bufferIndex = mVideoEncoder.dequeueOutputBuffer(mVideoBufferInfo, 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(mVideoEncoder.getOutputFormat());
if (!mMuxerStarted && mTrackIndex >= 0) {
mMuxer.start();
mMuxerStarted = true;
}
} else if (bufferIndex < 0) {
// not sure what's going on, ignore it
} else {
ByteBuffer encodedData = mVideoEncoder.getOutputBuffer(bufferIndex);
if (encodedData == null) {
throw new RuntimeException("couldn't fetch buffer at index " + bufferIndex);
}

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

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

mVideoEncoder.releaseOutputBuffer(bufferIndex, false);

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

mDrainHandler.postDelayed(mDrainEncoderRunnable, 10);
return false;
}

private void releaseEncoders() {
mDrainHandler.removeCallbacks(mDrainEncoderRunnable);
Toast.makeText(this, "Stopping recording", Toast.LENGTH_SHORT).show();
if (mMuxer != null) {
if (mMuxerStarted) {
mMuxer.stop();
}
mMuxer.release();
mMuxer = null;
mMuxerStarted = false;
}
if (mVideoEncoder != null) {
mVideoEncoder.stop();
mVideoEncoder.release();
mVideoEncoder = null;
}
if (mInputSurface != null) {
mInputSurface.release();
mInputSurface = null;
}
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
mVideoBufferInfo = null;
mDrainEncoderRunnable = null;
mTrackIndex = -1;
}

Answer

This actually solved my problem.

private static final String TAG = "MainActivity";
private static final int REQUEST_CODE = 1000;
private int mScreenDensity;
private MediaProjectionManager mProjectionManager;
private static final int DISPLAY_WIDTH = 720;
private static final int DISPLAY_HEIGHT = 1280;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private MediaProjectionCallback mMediaProjectionCallback;
private ToggleButton mToggleButton;
private MediaRecorder mMediaRecorder;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final int REQUEST_PERMISSIONS = 10;

static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode != REQUEST_CODE) {
        Log.e(TAG, "Unknown request code: " + requestCode);
        return;
    }
    if (resultCode != RESULT_OK) {
        Toast.makeText(this,
                "Screen Cast Permission Denied", Toast.LENGTH_SHORT).show();
        mToggleButton.setChecked(false);
        return;
    }
    mMediaProjectionCallback = new MediaProjectionCallback();
    mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
    mMediaProjection.registerCallback(mMediaProjectionCallback, null);
    mVirtualDisplay = createVirtualDisplay();
    mMediaRecorder.start();
}

public void onToggleScreenShare(View view) {
    if (((ToggleButton) view).isChecked()) {
        initRecorder();
        shareScreen();
    } else {
        mMediaRecorder.stop();
        mMediaRecorder.reset();
        Log.v(TAG, "Stopping Recording");
        stopScreenSharing();
    }
}

private void shareScreen() {
    if (mMediaProjection == null) {
        startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
        return;
    }
    mVirtualDisplay = createVirtualDisplay();
    mMediaRecorder.start();
}

private VirtualDisplay createVirtualDisplay() {
    return mMediaProjection.createVirtualDisplay("MainActivity",
            DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            mMediaRecorder.getSurface(), null /*Callbacks*/, null
            /*Handler*/);
}

private void initRecorder() {
    try {
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mMediaRecorder.setOutputFile(Environment
                .getExternalStoragePublicDirectory(Environment
                        .DIRECTORY_DOWNLOADS) + "/video.mp4");
        mMediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mMediaRecorder.setVideoEncodingBitRate(512 * 1000);
        mMediaRecorder.setVideoFrameRate(30);
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        int orientation = ORIENTATIONS.get(rotation + 90);
        mMediaRecorder.setOrientationHint(orientation);
        mMediaRecorder.prepare();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private class MediaProjectionCallback extends MediaProjection.Callback {
    @Override
    public void onStop() {
        if (mToggleButton.isChecked()) {
            mToggleButton.setChecked(false);
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            Log.v(TAG, "Recording Stopped");
        }
        mMediaProjection = null;
        stopScreenSharing();
    }
}

private void stopScreenSharing() {
    if (mVirtualDisplay == null) {
        return;
    }
    mVirtualDisplay.release();
    //mMediaRecorder.release(); //If used: mMediaRecorder object cannot
    // be reused again
    destroyMediaProjection();
}

@Override
public void onDestroy() {
    super.onDestroy();
    destroyMediaProjection();
}

private void destroyMediaProjection() {
    if (mMediaProjection != null) {
        mMediaProjection.unregisterCallback(mMediaProjectionCallback);
        mMediaProjection.stop();
        mMediaProjection = null;
    }
    Log.i(TAG, "MediaProjection Stopped");
}

More info here