Chrystian
Chrystian

Reputation: 1047

MediaRecorder Android 11 start failed -1004

On Android 11 my MediaRecorder fails to initialize. I suspect the problem is related to scopedstorage, but I have been unable to figure out the cause. I am using MediaRecorder to record audio from the microphone. I extract the amplitude from the audio, so I have no intention to keep the file, that is why the path is /dev/null

 var mRecorder: MediaRecorder? = null


 if (mRecorder == null) {
        mRecorder = MediaRecorder()
        mRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
        mRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
        mRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
        mRecorder!!.setOutputFile("/dev/null")
        try {
            mRecorder!!.prepare()
        } catch (ioe: IOException) {
            Log.e("[Monkey]", "IOException: " + Log.getStackTraceString(ioe))
        } catch (e: SecurityException) {
            Log.e("[Monkey]", "SecurityException: " + Log.getStackTraceString(e))
        }
        try {
            mRecorder!!.start()
        } catch (e: SecurityException) {
            Log.e("[Monkey]", "SecurityException: " + Log.getStackTraceString(e))
        }

The crash is at MediaRecorded.start(). Is /dev/null not a valid path on Android 11?

Logcat:

start failed: -1004
2020-11-15 10:51:41.827 11836-11836/= E/AndroidRuntime: FATAL EXCEPTION: main
    Process: c=, PID: 11836
    java.lang.RuntimeException: start failed.
        at android.media.MediaRecorder.start(Native Method)
 

Upvotes: 12

Views: 4454

Answers (2)

Kaba
Kaba

Reputation: 83

For some reason setting the "/dev/null" path to prevent MediaRecorder from storing is causing the crash at Android 11 version to this date, by setting an actual path it get's fixed just like is mentioned in another answer, but In my case all I needed in my app was to register the amplitude levels coming from the microphone, so in order to prevent storing in the output file indeterminately I flush the MediaRecorder object every minute.

This a full but simplified example:

class MainActivity : AppCompatActivity() {

    private lateinit var mediaRecorder: MediaRecorder
    private var handler: Handler = Handler()

    private var fakeOutput = ""
    private var needsFlush = false
    private var flushCounter = 300

    private val runnable = object : Runnable {
        override fun run() {
            Log.d("AudioAmplitude: ", mediaRecorder.maxAmplitude.toString())
            if (needsFlush) {
                if (flushCounter == 0) {
                    mediaRecorder.stop()
                    mediaRecorder.release()
                    initMediaRecorder()
                    flushCounter = 300
                }
                flushCounter--
            }
            handler.postDelayed(this, 200)
        }
    }

    private val requestedPermissions = arrayOf(
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.CAMERA
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        requestedPermissions.forEach {
            ContextCompat.checkSelfPermission(
                this,
                it
            )
        }

        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.RECORD_AUDIO
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(
                    (this as Activity?)!!,
                    Manifest.permission.RECORD_AUDIO
                )
            ) {
            } else {
                ActivityCompat.requestPermissions(
                    (this as Activity?)!!, arrayOf(Manifest.permission.RECORD_AUDIO),
                    0
                )
            }
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            fakeOutput = "${externalCacheDir?.absolutePath}/temp.3gp"
            needsFlush = true
        } else {
            fakeOutput = "/dev/null"
        }

        initMediaRecorder()
    }

    private fun initMediaRecorder() {
        mediaRecorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            setOutputFile(fakeOutput)
            prepare()
            start()
        }
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun onResume() {
        super.onResume()
        mediaRecorder.resume()
        handler.post(runnable)
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun onPause() {
        super.onPause()
        mediaRecorder.pause()
        handler.removeCallbacks(runnable)
    }
}

Notice that I use a Handler (maybe not the best practice) to print out the maxAmplitude which is the main reason why I was working on this, I am using it to flush every 60 seconds (counter is 300 minus one every 200 ms the handler calls itself).

Upvotes: 2

Alexander
Alexander

Reputation: 314

Replace "/dev/null" with the correct file path "${externalCacheDir.absolutePath}/test.3gp" and it should work.

Upvotes: 7

Related Questions