You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android Studio音频存储问题:钢琴录制App保存失败排查

解决Android钢琴App录制音频保存失败、Uri为空的问题

Hey there, let's break down the issues causing your recording to fail and return a null Uri:

1. 录制路径指向的是目录而非具体文件

你的startRecording方法里,filePath被设置为direPath + "/recording",这会创建一个目录而非具体的音频文件。MediaRecorder需要写入到具体的文件路径,否则录制过程会静默失败,后续的audioFile实际上是不存在的,导致插入MediaStore时返回null。

修复代码:
修改文件名,添加时间戳和正确的后缀(对应你设置的THREE_GPP格式,后缀用.3gp):

private void startRecording() throws IOException { 
    try { 
        String direPath = context.getExternalFilesDir(null).getAbsolutePath(); 
        // 生成唯一的文件名,带3gp后缀
        String fileName = "recording_" + System.currentTimeMillis() + ".3gp";
        String filePath = direPath + File.separator + fileName; 
        audioFile = new File(filePath); 
    } catch (Exception e) { 
        Log.e("Error Tag", "external storage access error " + e.getMessage()); 
        return; 
    } 
    // 剩余MediaRecorder初始化代码不变...
}

File.separator代替硬编码的/,适配不同Android设备的文件系统分隔符。

2. Scoped Storage限制导致MediaStore插入失败(Android 10+)

从Android 10(API 29)开始,系统引入了Scoped Storage,直接设置MediaStore.Audio.Media.DATA字段会被系统忽略,这是导致contentResolver.insert返回null的核心原因之一。另外,你设置的MIME类型audio/mpeg和你使用的THREE_GPP输出格式不匹配(正确的MIME类型是audio/3gpp),这也可能导致MediaStore拒绝你的插入请求。

修复saveToDisk方法,适配Scoped Storage:

public void saveToDisk() { 
    ContentResolver contentResolver = context.getContentResolver(); 
    ContentValues values = new ContentValues(); 
    long current = System.currentTimeMillis(); 
    values.put(MediaStore.Audio.Media.TITLE, "audio_" + current); 
    values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000)); 
    values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp"); // 匹配THREE_GPP格式
    values.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (current / 1000));

    Uri uri = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10+ 使用RELATIVE_PATH指定存储位置(比如音乐目录下的自定义文件夹)
        values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/PianoRecordings");
        uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
        
        // 将录制好的文件复制到MediaStore管理的Uri位置
        if (uri != null) {
            try (OutputStream os = contentResolver.openOutputStream(uri);
                 InputStream is = new FileInputStream(audioFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
            } catch (IOException e) {
                Log.e("SaveError", "Failed to copy recording", e);
                uri = null;
            }
        }
    } else {
        // Android 9及以下,可继续使用DATA字段(需确保已申请WRITE_EXTERNAL_STORAGE权限)
        values.put(MediaStore.Audio.Media.DATA, audioFile.getAbsolutePath());
        uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
    }

    if (uri != null) {
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); 
        Toast.makeText(context, "Added File " + uri, Toast.LENGTH_LONG).show(); 
    } else {
        Toast.makeText(context, "Failed to save recording", Toast.LENGTH_LONG).show();
    }
}

3. 权限检查不可少

确保你已经处理了必要的权限:

  • Android 6.0+(API 23):动态申请RECORD_AUDIO权限(录制音频必须)
  • Android 9及以下:额外申请WRITE_EXTERNAL_STORAGE权限
  • Android 10+:无需WRITE_EXTERNAL_STORAGE权限,但要确保MediaStore的RELATIVE_PATH设置正确

在调用startRecording前,一定要检查权限是否已授予,否则会抛出IO异常或静默失败。


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

火山引擎 最新活动