获取音频文件的时长

38

我制作了一款录音应用程序,我想在列表视图中显示录音的持续时间。 我是这样保存录音的:

MediaRecorder recorder = new MediaRecorder();
recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
folder = new File(Environment.getExternalStorageDirectory()
            + File.separator + "Audio recordings");
String[] files = folder.list();
    int number = files.length + 1;
    String filename = "AudioSample" + number + ".mp3";
    File output = new File(Environment.getExternalStorageDirectory()
            + File.separator + "Audio recordings" + File.separator
            + filename);
    FileOutputStream writer = new FileOutputStream(output);
    FileDescriptor fd = writer.getFD();
    recorder.setOutputFile(fd);
    try {
        recorder.prepare();
        recorder.start();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        Log.e(LOG_TAG, "prepare() failed");
        e.printStackTrace();
    }

如何获取此文件的持续时间(以秒为单位)?

提前感谢。

---编辑 我搞定了,我在MediaPlayer.setOnPreparedListener()方法中调用了MediaPlayer.getDuration(),所以它返回了0。

13个回答

98

MediaMetadataRetriever 是一种轻量级和高效的方法。在需要高性能的环境中,例如滚动、分页、列表等情况下,MediaPlayer 太重了,可能会导致性能问题。

此外,由于 MediaPlayer 太重了,有时需要不断重启,因此可能会出现 Error (100,0) 的问题。

Uri uri = Uri.parse(pathStr);
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(AppContext.getAppContext(),uri);
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
int millSecond = Integer.parseInt(durationStr);

1
很高兴发现这一点,因为MediaPlayer.getDuration()似乎存在错误——至少对于mp4文件是这样的。谢谢! - steven smith
我不知道MediaMetadataCompat,但是MediaMetadataRetriever.METADATA_KEY_DURATION已经过时了。最好像这样获取持续时间:`String durationStr = mmr.ExtractMetadata(MetadataKey.Duration);` - dstrube
@dstrube 你从哪里得到这个信息的?根据Android文档,它并没有被弃用。https://developer.android.com/reference/android/media/MediaMetadataRetriever#METADATA_KEY_DURATION - ka3ak
@ka3ak 嗯,这很奇怪。我不知道我从哪里得到的。那是几个月前的事了,所以我无法确定在什么代码中看到一个警告说它已经过时了。(快速搜索我的一些常见代码并没有找到任何关于METADATA_KEY_DURATION或ExtractMetadata的结果。) 我撤回我的评论,并感谢您纠正我。 - dstrube
https://pub.dev/packages/media_metadata_retriever 显示该软件包已经停止维护。 - Kamlesh
显示剩余2条评论

40

最快的方法是使用 MediaMetadataRetriever。但是,有一个注意点

如果您使用URI和context设置数据源,则可能会遇到错误 https://code.google.com/p/android/issues/detail?id=35794

解决办法是使用文件的绝对路径来检索媒体文件的元数据。

以下是执行此操作的代码片段

 private static String getDuration(File file) {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                mediaMetadataRetriever.setDataSource(file.getAbsolutePath());
                String durationStr = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                return Utils.formateMilliSeccond(Long.parseLong(durationStr));
            }

现在您可以使用以下任意一种格式将毫秒转换为人类可读格式

     /**
         * Function to convert milliseconds time to
         * Timer Format
         * Hours:Minutes:Seconds
         */
        public static String formateMilliSeccond(long milliseconds) {
            String finalTimerString = "";
            String secondsString = "";

            // Convert total duration into time
            int hours = (int) (milliseconds / (1000 * 60 * 60));
            int minutes = (int) (milliseconds % (1000 * 60 * 60)) / (1000 * 60);
            int seconds = (int) ((milliseconds % (1000 * 60 * 60)) % (1000 * 60) / 1000);

            // Add hours if there
            if (hours > 0) {
                finalTimerString = hours + ":";
            }

            // Prepending 0 to seconds if it is one digit
            if (seconds < 10) {
                secondsString = "0" + seconds;
            } else {
                secondsString = "" + seconds;
            }

            finalTimerString = finalTimerString + minutes + ":" + secondsString;

    //      return  String.format("%02d Min, %02d Sec",
    //                TimeUnit.MILLISECONDS.toMinutes(milliseconds),
    //                TimeUnit.MILLISECONDS.toSeconds(milliseconds) -
    //                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(milliseconds)));

            // return timer string
            return finalTimerString;
        }

1
太棒了!这个解决方案对我非常有用。只需要从文件中提取元数据,而不需要繁琐和沉重的MediaPlayer。 - sud007

24

要以毫秒为单位获取持续时间,请尝试以下方法:

MediaPlayer mp = MediaPlayer.create(yourActivity, Uri.parse(pathofyourrecording));
int duration = mp.getDuration();

或者可以通过测量从recorder.start()recorder.stop()所经过的纳秒数来计算时间:

long startTime = System.nanoTime();    
// ... do recording ...    
long estimatedTime = System.nanoTime() - startTime;

第一个解决方案返回0,如果我想使用第二个解决方案,我必须播放文件,但我不想这样做。 - Simon
你不必为第二个文件播放它。当用户开始录制时,设置startTime并在用户停止录制时计算estimatedTime。对于第一个选项,请确保在初始化MediaPlayer之前释放MediaRecorder。如果仍然返回0,则可能存在错误。我建议尝试不同的音频格式。 - Erol
这个解决方案在一些设备上可以工作,但在其他设备上却不行。有什么想法为什么会这样?在Nexus5上可以工作,但在Hudl上却不行。可能是支持的媒体类型吗?我正在尝试读取的文件是一个H264 MP4。 - speedynomads

14
尝试使用。
long totalDuration = mediaPlayer.getDuration(); // to get total duration in milliseconds

long currentDuration = mediaPlayer.getCurrentPosition(); // to Gets the current playback position in milliseconds

除以1000,转换为秒。

希望这能帮到你。


1
MediaPlayer.getduration() 返回0,原因很奇怪,你知道为什么吗? - Simon
@simon 你必须在媒体开始时获取持续时间,如果太早就会返回-1。另外,@AwadKab 不幸的是,这并不总是一致的,因为对于 mp4,特别是 m3u8 容器的视频,存在错误持续时间的问题。 - greaterKing

5

Kotlin扩展解决方案

您可以添加此内容以可靠且安全地获取您的音频文件的持续时间。如果不存在或出现错误,则会返回0。

myAudioFile.getMediaDuration(context)

/**
 * If file is a Video or Audio file, return the duration of the content in ms
 */
fun File.getMediaDuration(context: Context): Long {
    if (!exists()) return 0
    val retriever = MediaMetadataRetriever()
    return try {
        retriever.setDataSource(context, uri)
        val duration = retriever.extractMetadata(METADATA_KEY_DURATION)
        retriever.release()
        duration.toLongOrNull() ?: 0
    } catch (exception: Exception) {
        0
    }
}

如果您经常使用String或Uri来处理文件,我建议还添加这些有用的助手函数。
fun Uri.asFile(): File = File(toString())

fun String?.asUri(): Uri? {
    try {
        return Uri.parse(this)
    } catch (e: Exception) {
        Sentry.captureException(e)
    }
    return null
}

fun String.asFile() = File(this)

MediaMetadataRetriever包已经停止使用。 - Kamlesh

4
如果音频来自于URL,只需等待准备好(on prepared)即可。
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
             length = mp.getDuration();
        }
});

2
Kotlin中最简单的方法(如果它是音频文件):
private fun getDuration(absolutePath: String): String {
    val retriever = MediaMetadataRetriever()
    retriever.setDataSource(absolutePath)
    //For format in string MM:SS
    val rawDuration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
    val duration = rawDuration.milliseconds
    return format("%02d:%02d", duration.inWholeMinutes, duration.inWholeSeconds % 60)
}

private fun getDurationInSeconds(absolutePath: String): Long {
    val retriever = MediaMetadataRetriever()
    retriever.setDataSource(absolutePath)
    //Return only value in seconds
    val rawDuration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
    return rawDuration.milliseconds.inWholeSeconds
}

2
根据Vijay的回答,该函数可以给出音频/视频文件的持续时间,但不幸的是,存在运行时异常的问题,因此我解决了这个问题,并且以下函数可以正常工作并返回音频或视频文件的确切持续时间。
public String getAudioFileLength(String path, boolean stringFormat) {
    StringBuilder stringBuilder = new StringBuilder();
    try {
        Uri uri = Uri.parse(path);
        MediaMetadataRetriever mmr = new MediaMetadataRetriever();
        mmr.setDataSource(HomeActivity.this, uri);
        String duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
        int millSecond = Integer.parseInt(duration);
        if (millSecond < 0) return String.valueOf(0); // if some error then we say duration is zero
        if (!stringFormat) return String.valueOf(millSecond);
        int hours, minutes, seconds = millSecond / 1000;
        hours = (seconds / 3600);
        minutes = (seconds / 60) % 60;
        seconds = seconds % 60;
        if (hours > 0 && hours < 10) stringBuilder.append("0").append(hours).append(":");
        else if (hours > 0) stringBuilder.append(hours).append(":");
        if (minutes < 10) stringBuilder.append("0").append(minutes).append(":");
        else stringBuilder.append(minutes).append(":");
        if (seconds < 10) stringBuilder.append("0").append(seconds);
        else stringBuilder.append(seconds);
    }catch (Exception e){
        e.printStackTrace();
    }
    return stringBuilder.toString();
}

:)


感谢您改进答案并为其他开发人员节省时间。 - Syed Rafaqat Hussain
感谢 Anwar 改进了答案,是的,在使用该函数时需要 try/catch。 - Vijay

2
你可以使用这个现成的方法,希望能帮到某些人。
示例1:getAudioFileLength(address, true); // 如果你想要字符串格式 示例2:getAudioFileLength(address, false); // 如果你想要毫秒格式
public String getAudioFileLength(String path, boolean stringFormat) {

            Uri uri = Uri.parse(path);
            MediaMetadataRetriever mmr = new MediaMetadataRetriever();
            mmr.setDataSource(Filter_Journals.this, uri);
            String duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
            int millSecond = Integer.parseInt(duration);

            if (millSecond < 0) return String.valueOf(0); // if some error then we say duration is zero

            if (!stringFormat) return String.valueOf(millSecond);

            int hours, minutes, seconds = millSecond / 1000;

            hours = (seconds / 3600);
            minutes = (seconds / 60) % 60;
            seconds = seconds % 60;

            StringBuilder stringBuilder = new StringBuilder();
            if (hours > 0 && hours < 10) stringBuilder.append("0").append(hours).append(":");
            else if (hours > 0) stringBuilder.append(hours).append(":");

            if (minutes < 10) stringBuilder.append("0").append(minutes).append(":");
            else stringBuilder.append(minutes).append(":");

            if (seconds < 10) stringBuilder.append("0").append(seconds);
            else stringBuilder.append(seconds);

            return stringBuilder.toString();
        }

1
对我而言,AudioGraph类拯救了我:
public static async Task<double> AudioFileDuration(StorageFile file)
        {
            var result = await AudioGraph.CreateAsync(new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Speech));
            if (result.Status == AudioGraphCreationStatus.Success)
            {
                AudioGraph audioGraph = result.Graph;
                var fileInputNodeResult = await audioGraph.CreateFileInputNodeAsync(file);
                return fileInputNodeResult.FileInputNode.Duration.TotalSeconds;
            }
            return -1;
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接