Search code examples
javaandroidmediarecorderandroid-external-storage

MediaRecorder prepare() failed /storage/emulated/0/: open failed: EPERM (Operation not permitted)


I am trying to record audio on my Android 12 Device with File and Media Permission granted but recorder.prepare(); throws

prepare() failed /storage/emulated/0/Music/Exotel/Media/Exotel Audio/Voice Messages/Exotel Temp/Exotel_Voice1658709937668.3gpp: open failed: EPERM (Operation not permitted)

I am using official documentation but they are saving recording into app specific storage(getExternalCacheDir().getAbsolutePath();) and i am saving on external public storage String TempPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()+ "/" + subfolder; . No matter what i try i still get the same error whenever i tried to save into external shared storage.

Here is my code:

String subfolder = "Exotel/Media/Exotel Audio/Voice Messages/Exotel Temp";
String time = new SimpleDateFormat("yyyyMMddhhmmss", Locale.US).format(new Date());
String filename = Session.getUserFname()+"_Voice"+time+".3gp";
String TempPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()+ "/" + subfolder;
File dir = new File(TempPath);

if (!dir.exists()){
    dir.mkdirs();
}

TempPath = TempPath+"/"+filename;
Log.d(TAG, "onTouch: Temp Path "+TempPath);

recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setOutputFile(TempPath);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

try {
    recorder.prepare();
    recorder.start();
} catch (IOException | IllegalStateException e) {
    Log.e(TAG, "prepare() failed "+e.getMessage());
}

Solution

  • The getExternalStoragePublicDirectory() method is deprecated in API level 29 (Android 10).

    If you want to save a file into a public shareable directory you have to use MediaStore API. I also recommend taking a look at all options for storing files on Android.

    If you have no problems with saving your recordings into a cache directory (by using the code from the official docs). Then you can copy the file from the cache to the public directory using MediaStore API:

    private void copyToPublicDirectory(String filename) throws IOException {
        InputStream inputStream = createInputStream(filename);
        OutputStream outputStream = createOutputStream(filename);
        copy(inputStream, outputStream);
    }
    
    private InputStream createInputStream(String filename) throws IOException {
        File cacheDirectory = getExternalCacheDir();
        if (cacheDirectory == null)
            throw new RuntimeException("Cache is not currently available");
        return new FileInputStream(new File(cacheDirectory, filename));
    }
    
    private OutputStream createOutputStream(String filename) throws IOException {
        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, filename);
        Uri contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        Uri uri = getContentResolver().insert(contentUri, contentValues);
        if (uri == null)
            throw new RuntimeException("Cannot insert file: " + filename);
        OutputStream outputStream = getContentResolver().openOutputStream(uri);
        if (outputStream == null)
            throw new RuntimeException("Cannot open uri: " + uri);
        return outputStream;
    }
    
    private void copy(InputStream source, OutputStream target) throws IOException {
        byte[] buffer = new byte[8192];
        int length;
        while ((length = source.read(buffer)) != -1) {
            target.write(buffer, 0, length);
        }
    }
    

    Note: 3gp recordings have video/3gpp mime type. And it will be saved into the Movies directory (as @blackapps already said). If you want to record a file in audio format and save it into the Music directory, then do the following:

    1. Change content uri from MediaStore.Video.Media.EXTERNAL_CONTENT_URI to MediaStore.Audio.Media.EXTERNAL_CONTENT_URI in the code above.

    2. Use OGG format in your MediaRecorder setup (OGG format is available only for Android API 29 and higher):

      setOutputFormat(MediaRecorder.OutputFormat.OGG)
      setAudioEncoder(MediaRecorder.AudioEncoder.OPUS)
      

    PS: don't forget to add all necessary permissions to make things work.