Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.lang.IllegalStateException: failed to get surface

Tags:

java

android

I am trying to create an app which enables the user to record his smartphones's screen. This is my starting code:

   import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity
{

    private static final int CAST_PERMISSION_CODE = 22;
    private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private MediaRecorder mMediaRecorder;
    private MediaProjectionManager mProjectionManager;

    private Button startButton;

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

        startButton = (Button) findViewById( R.id.recordButton );

        mMediaRecorder = new MediaRecorder();

        mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        getWindowManager().getDefaultDisplay().getMetrics(this.mDisplayMetrics);

        prepareRecording();
        startRecording();
    }

    private void startRecording() {
        // If mMediaProjection is null that means we didn't get a context, lets ask the user
        if (mMediaProjection == null) {
            // This asks for user permissions to capture the screen
            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), CAST_PERMISSION_CODE);
            return;
        }
        mVirtualDisplay = getVirtualDisplay();
        mMediaRecorder.start();
    }

    private void stopRecording() {
        if (mMediaRecorder != null) {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
        }
        if (mVirtualDisplay != null) {
            mVirtualDisplay.release();
        }
        if (mMediaProjection != null) {
            mMediaProjection.stop();
        }
        prepareRecording();
    }

    public String getCurSysDate() {
        return new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date());
    }

    private void prepareRecording() {
        try {
            mMediaRecorder.prepare();
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        final String directory = Environment.getExternalStorageDirectory() + File.separator + "Recordings";
        if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            Toast.makeText(this, "Failed to get External Storage", Toast.LENGTH_SHORT).show();
            return;
        }
        final File folder = new File(directory);
        boolean success = true;
        if (!folder.exists()) {
            success = folder.mkdir();
        }
        String filePath;
        if (success) {
            String videoName = ("capture_" + getCurSysDate() + ".mp4");
            filePath = directory + File.separator + videoName;
        } else {
            Toast.makeText(this, "Failed to create Recordings directory", Toast.LENGTH_SHORT).show();
            return;
        }

        int width = mDisplayMetrics.widthPixels;
        int height = mDisplayMetrics.heightPixels;

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mMediaRecorder.setVideoEncodingBitRate(512 * 1000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(width, height);
        mMediaRecorder.setOutputFile(filePath);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != CAST_PERMISSION_CODE) {
            // Where did we get this request from ? -_-
            //Log.w(TAG, "Unknown request code: " + requestCode);
            return;
        }
        if (resultCode != RESULT_OK) {
            Toast.makeText(this, "Screen Cast Permission Denied :(", Toast.LENGTH_SHORT).show();
            return;
        }
        mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
        // TODO Register a callback that will listen onStop and release & prepare the recorder for next recording
        // mMediaProjection.registerCallback(callback, null);
        mVirtualDisplay = getVirtualDisplay();
        mMediaRecorder.start();
    }

    private VirtualDisplay getVirtualDisplay()
    {
        int screenDensity = mDisplayMetrics.densityDpi;
        int width = mDisplayMetrics.widthPixels;
        int height = mDisplayMetrics.heightPixels;

        return mMediaProjection.createVirtualDisplay(this.getClass().getSimpleName(), width, height, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder.getSurface(), null /*Callbacks*/, null /*Handler*/);
    }

}

After showing a message that informs the user about the screen capture function, my app crushes.

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=22, result=-1, data=Intent { (has extras) }} to activity {gr.awm.clrecorder/gr.awm.clrecorder.MainActivity}: java.lang.IllegalStateException: failed to get surface
                                                                   at android.app.ActivityThread.deliverResults(ActivityThread.java:3974)
                                                                   at android.app.ActivityThread.handleSendResult(ActivityThread.java:4017)
                                                                   at android.app.ActivityThread.access$1400(ActivityThread.java:172)
                                                                   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1471)
                                                                   at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                   at android.os.Looper.loop(Looper.java:145)
                                                                   at android.app.ActivityThread.main(ActivityThread.java:5832)
                                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                                   at java.lang.reflect.Method.invoke(Method.java:372)
                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
                                                                Caused by: java.lang.IllegalStateException: failed to get surface
                                                                   at android.media.MediaRecorder.getSurface(Native Method)
                                                                   at gr.awm.clrecorder.MainActivity.getVirtualDisplay(MainActivity.java:148)
                                                                   at gr.awm.clrecorder.MainActivity.onActivityResult(MainActivity.java:135)

Is there a way to solve this issue? Any advice would be helpful and deeply appreciated. Thanks in advance

like image 900
PL13 Avatar asked Feb 29 '16 23:02

PL13


2 Answers

Nevermind the comment btw.

I dug into the documentation and your code and got the following results.

This is the order you call the mMediaRecorder methods to get a surface.

mMediaRecorder.prepare();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setVideoEncodingBitRate(512 * 1000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(width, height);
mMediaRecorder.setOutputFile(filePath);

This is what the documentation says

//Call this method before prepare().
setVideoEncodingBitRate();  //no exception thrown

//Must be called after setVideoSource(). Call this after setOutFormat() but before prepare().
setVideoSize(width, height);  //IllegalStateException if it is called after prepare() or before setOutputFormat() 

//Call this only before setOutputFormat().
setAudioSource(); //IllegalStateException if it is called after setOutputFormat()
setVideoSource(); //IllegalStateException if it is called after setOutputFormat()

//Call this after setOutputFormat() and before prepare().
setVideoEncoder(); //IllegalStateException if it is called before setOutputFormat() or after prepare()
setAudioEncoder(); //IllegalStateException if it is called before setOutputFormat() or after prepare().

//Call this after setAudioSource()/setVideoSource() but before prepare(). 
setOutputFormat(); //IllegalStateException if it is called after prepare() or before setAudioSource()/setVideoSource().

//Call this after setOutputFormat() but before prepare().
setOutputFile(); //IllegalStateException if it is called before setOutputFormat() or after prepare() 

//Must be called after setVideoSource(). Call this after setOutFormat() but before prepare().
setVideoFrameRate(); //IllegalStateException if it is called after prepare() or before setOutputFormat().

//This method must be called after setting up the desired audio and video sources, encoders, file format, etc., but before start()
prepare()  //IllegalStateException if it is called after start() or before setOutputFormat().

So in order to get the mMediaRecorder in a correct state you have to call the methods in this order:

setAudioSource()

setVideoSource()

setOutputFormat()

setAudioEncoder()

setVideoEncoder()

setVideoSize()

setVideoFrameRate()

setOutputFile()

setVideoEncodingBitRate()

prepare()

start()

I think I also got an undocumented error when I called the setEncoder Methods before the setSource Methods

Edit: I thought I got working code, but I still get IllegalStateExceptions although the code is in the order of the documentation.

Edit2: I got it working now. Things that might also not working and additional error messaages:

  • Permissions for external Storage and Microphone not set (add use-permissions in the Manifest)
  • Android MediaRecorder start failed in invalid state 4
  • IllegalStateException [start called in an invalid state: 1] on restarting Android MediaRecorder

I had to create a directory where the App could write to. I couldn't get the external Storage to work so I used the data directory. But that is unrelated to the mMediaRecorder code

This code works:

private void prepareRecording() {

    //Deal with FileDescriptor and Directory here        

    //Took audio out because emulator has no mic
    //mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

    mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

    mMediaRecorder.setVideoEncodingBitRate(512 * 1000);

    //mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

    mMediaRecorder.setVideoSize(width, height);
    mMediaRecorder.setVideoFrameRate(30);
    mMediaRecorder.setOutputFile(filePath);

    try {
        mMediaRecorder.prepare();
    } catch (Exception e) {
        e.printStackTrace();
        return;
    }

    //Field variable to hold surface object
    //Deal with it as you see fit
    surface = mMediaRecorder.getSurface();

Beware Although the above code works in creating the MediaRecorder correctly and writing to storage, it crashes the whole emulator when mMediaRecorder.stop() is called.

like image 133
ma cılay Avatar answered Oct 18 '22 18:10

ma cılay


Maybe you set a wrong video size or a wrong video source. make sure mediaRecord.prepare() has been execute successfully before.

I also get the problem, After check all above,I fixed the issue.

like image 22
huangyehui Avatar answered Oct 18 '22 19:10

huangyehui