🔥
(Swift)AVAudioPCMBufferのframeLengthがframeCapacityと一致しない事象について
結論
AVAudioPCMBufferにWAV形式の音声データを読み込む際、不具合と思われる事象に遭遇しました。お急ぎの方のため、先に結論をまとめます。
- AVAudioFileのreadメソッドで音声データを読み取る場合、モノラル音声はframeLengthとframeCapacityが一致しない。
- モノラル音声であればサンプリング周波数や量子化ビット数に関係なく事象が発生する。
- 読み取りが欠損するサンプル数はバッファの長さに関係なく、常に1,000サンプルほど欠損する。
- ステレオ音声はこの事象が発生しない。
- 事象を回避するには少し余分にバッファを確保する。
環境
以下の環境にて事象が発生することを確認しました。
- macOS 12.7.6 Monterey / Swift 5.7.2
- macOS 14.6.6 Sonoma / Swift 5.10
事象の再現
以下のコードをaudio.swiftとして保存してください。
import AVFoundation
if CommandLine.arguments.count < 2 {
fatalError("specify file name")
}
let audioFileURL = URL(fileURLWithPath: CommandLine.arguments[1])
guard
let audioFile = try? AVAudioFile(
forReading: audioFileURL, commonFormat: .pcmFormatInt16, interleaved: false)
else {
fatalError("failed to read the file: \(audioFileURL)")
}
guard
let audioBuffer = AVAudioPCMBuffer(
pcmFormat: audioFile.processingFormat, frameCapacity: AVAudioFrameCount(audioFile.length))
else {
fatalError("failed to build the audio buffer")
}
try audioFile.read(into: audioBuffer)
print("AVAudioFile.length:", audioFile.length)
print("AVAudioPCMBuffer.frameCapacity:", audioBuffer.frameCapacity)
print("AVAudioPCMBuffer.frameLength:", audioBuffer.frameLength)
guard let inputChannelData = audioBuffer.int16ChannelData else {
fatalError("failed to retrieve the mutable pointer")
}
let input = Array(
UnsafeBufferPointer(start: inputChannelData[0], count: Int(audioBuffer.frameLength)))
print("Signal:", input.suffix(10))
soxコマンドで適当な音声ファイルを用意します。Homebrewを導入済みであればbrew install sox
でインストールできます。
# 440 Hzサイン波が記録された48 kHz / 16 bit / 1 chのWAVファイルを作成
$ sox -r 48000 -c 1 -b 16 -n 48k1ch1sec.wav synth sin 440 trim 0 1
実行します。
$ swift audio.swift 48k1ch1sec.wav
以下のような結果が得られます。
$ swift audio.swift 48k1ch1sec.wav
AVAudioFile.length: 48000
AVAudioPCMBuffer.frameCapacity: 48000
AVAudioPCMBuffer.frameLength: 47082
実行例
参考までに、どのような値が得られるか示します。
パターン | AVAudioFile.length | AVAudioPCMBuffer.frameCapacity | AVAudioPCMBuffer.frameLength |
---|---|---|---|
24 kHz / 1 ch / 1 sec | 24000 | 24000 | 24000 |
24 kHz / 1 ch / 100 sec | 2400000 | 2400000 | 2398186 |
48 kHz / 1 ch / 1 sec | 48000 | 48000 | 47082 |
48 kHz / 1 ch / 10 sec | 4800000 | 4800000 | 4798442 |
96 kHz / 1 ch / 1 sec | 96000 | 96000 | 94186 |
96 kHz 1 ch / 10 sec | 960000 | 960000 | 958442 |
対処法
根本的な解決策ではありませんが、バッファを少し余分に確保することで今回の事象を回避できます。例えば、以下のように変更します。
// 変更前
let audioBuffer = AVAudioPCMBuffer( pcmFormat: audioFile.processingFormat, frameCapacity: AVAudioFrameCount(audioFile.length))
// 変更後(バッファを1%余分に確保)
let audioBuffer = AVAudioPCMBuffer( pcmFormat: audioFile.processingFormat, frameCapacity: AVAudioFrameCount(audioFile.length + audioFile.length / 100))
Discussion