🔥

(Swift)AVAudioPCMBufferのframeLengthがframeCapacityと一致しない事象について

2024/08/31に公開

結論

AVAudioPCMBufferにWAV形式の音声データを読み込む際、不具合と思われる事象に遭遇しました。お急ぎの方のため、先に結論をまとめます。

  1. AVAudioFileのreadメソッドで音声データを読み取る場合、モノラル音声はframeLengthとframeCapacityが一致しない。
  2. モノラル音声であればサンプリング周波数や量子化ビット数に関係なく事象が発生する。
  3. 読み取りが欠損するサンプル数はバッファの長さに関係なく、常に1,000サンプルほど欠損する。
  4. ステレオ音声はこの事象が発生しない。
  5. 事象を回避するには少し余分にバッファを確保する。

環境

以下の環境にて事象が発生することを確認しました。

  • 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))

参考資料

  1. AVAudioPCMBuffer - Apple Developer Documentation
  2. frameLength - Apple Developer Documentation

Discussion