You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 12设备录制音频至外部公共存储遇EPERM权限错误

问题描述

在已授予File and Media Permission权限的Android 12设备上,调用recorder.prepare()时抛出权限错误:

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

当前代码尝试将音频保存到外部公共存储的Music目录子文件夹,使用Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)获取路径,但始终触发相同错误。核心代码如下:

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;

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

错误原因

Android 12(API 31)强化了分区存储规则:

  • 即便授予了READ_MEDIA_AUDIO等File and Media权限,直接通过File API写入公共媒体目录(如Music、Pictures)会被系统拒绝,这类共享媒体区域必须通过MediaStore API操作。
  • Environment.getExternalStoragePublicDirectory()在Android Q(API 29)已废弃,Android 12中完全限制了该路径的直接写入权限。

解决方案

方案1:使用MediaStore API写入公共Music目录(推荐)

这是符合Android 12+存储规范的标准做法,无需额外特殊权限,仅需已有的File and Media权限即可。

示例代码:

// 定义文件名和相对路径
String time = new SimpleDateFormat("yyyyMMddhhmmss", Locale.US).format(new Date());
String filename = Session.getUserFname() + "_Voice" + time + ".3gp";
String relativePath = Environment.DIRECTORY_MUSIC + "/Exotel/Media/Exotel Audio/Voice Messages/Exotel Temp";

// 构建MediaStore插入元数据
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Audio.Media.RELATIVE_PATH, relativePath);
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp");
values.put(MediaStore.Audio.Media.IS_PENDING, 1); // 标记为待处理,避免媒体扫描干扰

// 获取音频文件的内容URI
Uri audioUri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);

// 初始化并配置MediaRecorder
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

try {
    // 通过内容URI获取文件描述符,设置给MediaRecorder
    if (audioUri != null) {
        try (OutputStream os = getContentResolver().openOutputStream(audioUri)) {
            if (os instanceof FileOutputStream) {
                FileDescriptor fd = ((FileOutputStream) os).getFD();
                recorder.setOutputFile(fd);
                recorder.prepare();
                recorder.start();
            }
        }
    }
} catch (IOException | IllegalStateException e) {
    Log.e(TAG, "prepare() failed " + e.getMessage());
    // 失败时清理临时条目
    if (audioUri != null) {
        getContentResolver().delete(audioUri, null, null);
    }
}

// 录制完成后,标记文件为已完成(允许媒体扫描)
if (audioUri != null) {
    ContentValues updateValues = new ContentValues();
    updateValues.put(MediaStore.Audio.Media.IS_PENDING, 0);
    getContentResolver().update(audioUri, updateValues, null, null);
}

方案2:申请MANAGE_EXTERNAL_STORAGE权限(不推荐)

仅适用于文件管理器、备份类等必须全局访问存储的应用,Google Play审核会严格限制该权限的使用。

  1. 在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
  1. 动态请求权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    if (!Environment.isExternalStorageManager()) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivityForResult(intent, 1001);
    }
}

权限通过后,即可使用原有的File API代码进行写入操作。


内容的提问来源于stack exchange,提问作者Ritu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 00:06:26