👌
(Swift)AVAudioFileを利用してwav形式の音声ファイルを保存する
実装例
例として、/tmp/input.wav
を読み込み、同じ内容を/tmp/output.wav
として保存する処理を示します。
import AVFoundation
func readPCMBuffer(url: URL) -> AVAudioPCMBuffer? {
guard let input = try? AVAudioFile(forReading: url, commonFormat: .pcmFormatInt16, interleaved: false) else {
return nil
}
guard let buffer = AVAudioPCMBuffer(pcmFormat: input.processingFormat, frameCapacity: AVAudioFrameCount(input.length)) else {
return nil
}
do {
try input.read(into: buffer)
} catch {
return nil
}
return buffer
}
func writePCMBuffer(url: URL, buffer: AVAudioPCMBuffer) throws {
let settings: [String: Any] = [
AVFormatIDKey: buffer.format.settings[AVFormatIDKey] ?? kAudioFormatLinearPCM,
AVNumberOfChannelsKey: buffer.format.settings[AVNumberOfChannelsKey] ?? 2,
AVSampleRateKey: buffer.format.settings[AVSampleRateKey] ?? 44100,
AVLinearPCMBitDepthKey: buffer.format.settings[AVLinearPCMBitDepthKey] ?? 16
]
do {
let output = try AVAudioFile(forWriting: url, settings: settings, commonFormat: .pcmFormatInt16, interleaved: false)
try output.write(from: buffer)
} catch {
throw error
}
}
func copyPCMBuffer(from inputPath: String, to outputPath: String) {
guard let inputBuffer = readPCMBuffer(url: URL(string: inputPath)!) else {
fatalError("failed to read \(inputPath)")
}
guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: inputBuffer.format, frameCapacity: inputBuffer.frameLength) else {
fatalError("failed to create a buffer for writing")
}
guard let inputInt16ChannelData = inputBuffer.int16ChannelData else {
fatalError("failed to obtain underlying input buffer")
}
guard let outputInt16ChannelData = outputBuffer.int16ChannelData else {
fatalError("failed to obtain underlying output buffer")
}
for channel in 0 ..< Int(inputBuffer.format.channelCount) {
let p1: UnsafeMutablePointer<Int16> = inputInt16ChannelData[channel]
let p2: UnsafeMutablePointer<Int16> = outputInt16ChannelData[channel]
for i in 0 ..< Int(inputBuffer.frameLength) {
p2[i] = p1[i]
}
}
outputBuffer.frameLength = inputBuffer.frameLength
do {
try writePCMBuffer(url: URL(string: outputPath)!, buffer: outputBuffer)
} catch {
fatalError("failed to write \(outputPath)")
}
}
copyPCMBuffer(from: "file:///tmp/input.wav", to: "file:///tmp/output.wav")
実装の注意点
以下の2点に注意してください。
- AVAudioFileのsettings引数(実装例の28行目)
- 書き込み用バッファのframeLength(実装例の57行目)
1.について、WAV形式のファイルを書き込む場合は少なくとも以下の4つのパラメータが必要です。
- AVFormatIDKey
- AVNumberOfChannelsKey
- AVSampleRateKey
- AVLinearPCMBitDepthKey
なお、settingsパラメータとしてbuffer.format.settings
をそのまま使うこともできますが、実行時に警告が表示されます。AVAudioFileのinterleaved引数とbuffer.format.settings[AVLinearPCMIsNonInterleaved]
の値が衝突するためです。
2.についてはバッファに書き込んだフレーム数を設定する必要があります。設定を忘れた場合、すなわちframeLengthが0の場合は空のファイルが作成されます。
今後のアップデートで修正されるかもしれませんが、現状はバッファのframeLengthが0でも書き込みは成功します。エラーは発生しないため注意してください。
Discussion