💭

(Swift)AVAudioPlayerでaacファイルの再生時間を取得すると間違った値が返される場合の対処法

2022/02/05に公開

はじめに

以下の環境で動作確認しました。

  • Xcode 13.2.1 (13C100)
  • iOS 15.1
  • macOS 12.1 Monterey

AVAudioPlayerはiOSとmacOSで同じ挙動をします。そこで、この記事ではmacOSのターミナルで動作確認することにします。

実験

まずは長さ100秒のwavファイルとaacファイルを作成します。

$ sox -n /tmp/audio.wav pinknoise trim 0 100
$ ffmpeg -i audio.wav audio.aac

次に、それぞれの再生時間をAVAudioPlayer.durationを用いて取得します。以下のコードをmain.swiftとして保存してください。

import AVFoundation

let wav = URL(string: "file:///tmp/audio.wav")!
let aac = URL(string: "file:///tmp/audio.aac")!

guard let wavPlayer = try? AVAudioPlayer(contentsOf: wav) else {
  fatalError("failed to open \(wav)")
}
guard let aacPlayer = try? AVAudioPlayer(contentsOf: aac) else {
  fatalError("failed to open \(aac)")
}

print("audio.wav \(wavPlayer.duration)")
print("audio.aac \(aacPlayer.duration)")

実行すると以下のように表示されます。

$ swift main.swift
audio.wav 100.0
audio.aac 87.78304612706702

wavファイルの再生時間は正しい値が取得できています。一方、aacファイルの再生時間は間違っています。

対処法

以下のようにAVAudioPlayer.prepareToPlay()を実行してからdurationを取得すると正しい値が取得できます。

aacPlayer.prepareToPlay()
print("audio.aac \(aacPlayer.duration)")

実行すると以下のように表示されます。

$ swift main.swift
audio.wav 100.0
audio.aac 99.53944922572208

prepareToPlayメソッドは文字通り再生の準備、すなわち音声ファイルをメモリに読み込む処理をします。しかし、再生時間を取得するためだけにファイル全体を読み込むのは非効率です。なにか良い対処法をご存知の方、教えていただけると助かります。

(余談)aacファイルの構造上、正しい再生時間を取得するのは難しい

AppleはAVAudioPlayerの不具合を放置しているのでしょうか?試しにMP3ファイルやFLACファイルの再生時間を取得してみます。

# wavファイルをmp3・flacファイルに変換
$ sox audio.wav audio.mp3
$ sox audio.wav audio.flac

# コード内のaac・wavをmp3・flacに置換してから実行
$ swift main.swift
audio.mp3 100.032
audio.flac 100.0

mp3ファイルとflacファイルの再生時間は正しく取得できています。

詳細は省きますが、aacファイルは構造上、正しい再生時間を取得するのが難しいそうです。Appleは不具合を放置しているわけではありません。とはいえ意図した再生時間が取得できないのは不便なので何か対策してほしいところです。

参考資料

  1. AVAudioPlayer - Apple Developer

Discussion