🕌

(Swift)wav形式の音声ファイルをInt16の配列として読み込む

2021/03/13に公開

はじめに

以下の環境で動作検証しました。

$ swift --version
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0

WAV形式の音声ファイルをInt16の配列として読み込む

import AVFoundation

func readWavFile(_ path: String) -> [[Int16]]? {
    guard let url = URL(string: path) else {
        return nil
    }
    guard let audio = try? AVAudioFile(forReading: url, commonFormat: .pcmFormatInt16, interleaved: false) else {
        return nil
    }
    guard let buffer = AVAudioPCMBuffer(pcmFormat: audio.processingFormat, frameCapacity: AVAudioFrameCount(audio.length)) else {
        return nil
    }
    do {
        try audio.read(into: buffer)
    } catch {
        return nil
    }
    guard let int16ChannelData = buffer.int16ChannelData else {
        return nil
    }

    var channelData = [[Int16]]()

    for i in 0 ..< Int(audio.processingFormat.channelCount) {
        channelData.append(Array(UnsafeBufferPointer(start: int16ChannelData[i], count: Int(buffer.frameLength))))
    }

    return channelData
}

readWavFile()の使用例

func printSignal(_ path: String) {
    guard let signal = readWavFile(path) else {
        return
    }
    if signal.count < 2 {
        return
    }

    print("Left\tRight")

    for i in 0 ..< signal[0].count {
        print(String(format: "%d\t%d", signal[0][i], signal[1][i]))
    }
}

printSignal("/tmp/music.wav")

コードはgistにも公開してあります。

(余談その1)AVAudioFileのコンストラクタについて

AVAudioFileのコンストラクタは2種類あります。

  1. init(forReading: URL, commonFormat: AVAudioCommonFormat, interleaved: Bool)
  2. init(forReading: URL)

WAV形式の音声ファイルには16 bit integerや32 bit floatなどの符号化方式があります。1番目のコンストラクタのcommonFormatはファイルを読み込むときに使用する符号化方式を指定するためのパラメータです。

さて、2番目のコンストラクタにはファイル読み込みの符号化方式を指定するためのパラメータがありません。自動的に判別してくれるのでしょうか?残念ながら、そのような気の利いたことはしてくれません。

2番目のcommonFormatを指定しないコンストラクタの場合、ファイル読み込みの符号化方式は常に32 bit floatになります。この挙動はAppleのAPIリファレンスに書かれていないため、不具合なのか仕様なのか判断できません。確実に処理したいのであれば、1番目のコンストラクタを使ってください。

ファイルの読み込みが失敗するときはAVAudioFile.fileFormatAVAudioFile.processingFormatを比較してください。fileFormatにはファイル本来の符号化方式、processingFormatには読み込みに使用する符号化方式が格納されています。

(余談その2)interleaved

AVAudioFileのコンストラクタにはパラメータとしてinterleavedがあります。今まで知らなかったのですが、WAV形式の音声ファイルにはインターリーブとよばれる機能があるそうです。

通常、ステレオ音声ファイルにはL, R, L, R, ...のように左チャネルと右チャネルのサンプルが交互に記録されています。それに対してインターリーブが有効になっているとモノラルの音声が2つ収録されていると解釈されます。

(余談その3)量子化ビット数が一致しない場合の挙動

例えばAVAudioFileのcommonFormatとして16 bit integerを指定し、24 bit integerまたは32 bit integerで符号化されたファイルを読み込むと下位のビットは切り捨てられて16 bit integerとして読み込まれます。逆にビット数が足りない場合は0で埋められます。

おわりに

より短いコードで音声ファイルを読み込める方法があれば、ぜひ教えてください。

Discussion