Search code examples
androidandroid-5.0-lollipopscreen-recordingandroid-mediaprojection

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

Solution

  • 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