在iOS中如何获取音频文件的时长?

61
NSDictionary* fileAttributes = 
    [[NSFileManager defaultManager] attributesOfItemAtPath:filename 
                                                     error:nil]

通过文件属性键,您可以获取日期、大小等信息。但是如何获取持续时间?


1
你从哪里获取NSDictionary的数据? - James Bedford
1
它是iOS中的一种内置数据类型。它包含了文件的日期、大小等键值对。 - Namratha
2
我知道什么是NSDictionary。你从哪里检索这个NSDictionary的实例?你需要在问题中提供更多关于你尝试做什么的信息。 - James Bedford
1
p.s. 一个NSDictionary并不一定包含特定文件的数据和大小,它只是包含一组键值对的1:1映射。 - James Bedford
1
我已编辑我的问题以包含那些信息。谢谢并抱歉给您带来不便。 - Namratha
似乎持续时间不是由attributesOfItemAtPath返回的NSDictionary的一部分。 - Namratha
9个回答

146
NSFileManager类参考中的'文件属性键'中,您可以看到没有可用于返回歌曲持续时间的键。 NSFileManager实例获取有关文件的所有信息都与操作系统中实际文件本身的属性有关,例如其文件大小。 NSFileManager实际上不解释文件。
为了获得文件的持续时间,您需要使用知道如何解释文件的类。 AVFoundation框架提供了您需要的确切类AVAsset。 您可以使用具体子类AVURLAsset实例化此抽象类,然后向其提供指向要获取其持续时间的音频文件的NSURL。 然后,您可以通过查询其duration属性从AVAsset实例获取持续时间。
例如:
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioFileURL options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);

请注意,AVFoundation旨在作为一种高度异步的框架设计,以提高性能和整体用户体验。即使执行简单任务,如查询媒体文件的持续时间,也可能需要很长时间,并且可能导致应用程序挂起。您应该使用AVAsynchronousKeyValueLoading协议异步加载歌曲的持续时间,然后在完成处理程序块中更新您的UI。您应该查看'Block Programming Guide'以及名为“Discovering AV Foundation”的WWDC2010视频,该视频可在https://developer.apple.com/videos/wwdc/2010免费获取。

17
我总是得到0的持续时间。 - OMGPOP
这种方法会给我带来错误:FigByteFlumeCustomURLOpen signalled err=-12936 (kFigByteFlumeError_BadState) (no provider) at /SourceCache/CoreMedia/CoreMedia-1562.235/Prototypes/FigHTTP/FigByteFlumeCustomURL.c line 1486。 下面John Goodstadt的答案对我有效。 - Maksim
非常好的回答!非常简洁的方法,可以在不创建音频播放器实例的情况下获取持续时间。 - Christian Gossain
这样获取持续时间的方式可能会返回无效值。始终使用AVAsynchronousKeyValueLoading中的方法包装您的AVAssetAVAssetTrack访问。 - WeZZard
2
请确保除了AVFoundation外,还添加并导入CoreMedia框架。感谢这个有效的方法,James。 - Erik
显示剩余3条评论

19

对于仍在寻找的人。 基于答案,Swift 4的代码(包括从苹果文档中异步加载取值):

let audioAsset = AVURLAsset.init(url: yourURL, options: nil)

audioAsset.loadValuesAsynchronously(forKeys: ["duration"]) {
    var error: NSError? = nil
    let status = audioAsset.statusOfValue(forKey: "duration", error: &error)
    switch status {
    case .loaded: // Sucessfully loaded. Continue processing.
        let duration = audioAsset.duration
        let durationInSeconds = CMTimeGetSeconds(duration)
        print(Int(durationInSeconds))
        break              
    case .failed: break // Handle error
    case .cancelled: break // Terminate processing
    default: break // Handle all other cases
    }
}

16

您可以使用 Swift 实现相同的功能:

let audioAsset = AVURLAsset.init(url: audioFileURL, options: nil)
let duration = audioAsset.duration
let durationInSeconds = CMTimeGetSeconds(duration)

这对我不起作用。目前我不知道为什么,但是我得到了一个完全不同的音频文件持续时间。 - Baran Emre
3
这个方法会导致延迟。 - famfamfam
1
正如所说,如果您有许多资产,这将会造成严重的延迟。 - Whirlwind

14

为了完整性 - 获取mp3文件持续时间的另一种方法是:

NSURL * pathToMp3File = ...
NSError *error = nil;
AVAudioPlayer* avAudioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:pathToMp3File error:&error];

double duration = avAudioPlayer.duration; 
avAudioPlayer = nil;

我使用过这个,没有明显的延迟。


我知道问题是关于音频文件的,但以防万一:这对于.mov文件对我无效(而其他答案有效),否则这很好。 - newenglander
你需要使用"loadValuesAsynchronouslyForKeys"。 - LiangWang
2
这种方法不够准确,很多时候会导致持续时间偏差几秒钟。 - Khaled Annajar
@KhaledAnnajar,我刚刚经历了同样的事情。 - Maulik Rajani

10

Swift 5.0 + iOS 13:这是我遇到的唯一可行方法(采用@John Goodstadt提供的Swift解决方案)。目前我不确定原因,但使用以下代码记录音频文件(在我的情况下是语音备忘录)和接收音频文件之间平均有0.2秒的差异。

    do {
        let audioPlayer = try AVAudioPlayer(contentsOf: fileURL)
        return CGFloat(audioPlayer.duration)
    } catch {
        assertionFailure("Failed crating audio player: \(error).")
        return nil
    }

这太棒了。我在我的代码中稍微修改了一下:在定义audioPlayer之后返回CGFloat(audioPlayer?.duration ?? 0)。 - FontFamily
2
这会导致阻塞主线程,就像 Android 一样。 - famfamfam

3

iOS 15+解决方案

如果可以的话,请尝试这个更新的方法:

let asset = AVURLAsset(url: fileURL, options: nil)

// Returns a CMTime value.
let duration = try await asset.load(.duration)

// An array of AVMetadataItem for the asset.
let metadata = try await asset.load(.metadata)

// A CMTime value and an array of AVMetadataItem.
let (duration, metadata) = try await asset.load(.duration, .metadata)


注意:同时加载多个属性可使AVFoundation通过批量加载请求来优化性能。
这里有一份来自苹果文档的精彩指南异步加载媒体数据

1
使用AVAssetReader获取音频文件的时长。
guard let assetReader = try? AVAssetReader(asset: audioAsset) {
    return nil
}
let duration = Double(assetReader.asset.duration.value)
let timescale = Double(assetReader.asset.duration.timescale)
let totalDuration = duration / timescale
print(totalDuration)

1
我通过AVAudioRecorder录制了一个线性PCM文件(.pcm)。我借助Farhad Malekpour的帮助获取持续时间。也许这可以帮到你: iPhone: 获取音频文件的持续时间
NSURL *fileUrl = [NSURL fileURLWithPath:yourFilePath];
AudioFileID fileID;
OSStatus result = AudioFileOpenURL((__bridge CFURLRef)fileUrl,kAudioFileReadPermission, 0, &fileID);
Float64 duration = 0; //seconds. the duration of the audio.
UInt32 ioDataSize = sizeof(Float64);

result = AudioFileGetProperty(fileID, kAudioFilePropertyEstimatedDuration, 
&ioDataSize, &duration);
AudioFileClose(fileID);
if(0 == result) {
   //success
}
else {
  switch (result) {
    case kAudioFileUnspecifiedError:{
        //
    } break;
        // other cases...
    default:
        break;
  }
}

-1

我其实发现您可以直接使用“从音乐获取详细信息”块,并将其设置为“获取 ”的持续时间,其中 可以是来自提示的 mp3 输入,指向 mp3 的链接,共享表单中的 mp3 等等。


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