iOSでWAVファイルを書き出す場合、
以前はAudioToolKitにあるAudioFileCreateWithURLを使っていましたが、
ここでは試しにiOS8,macOS10.10で追加されたAVFoundationにあるAVAudioFileを使ってみます。
まず、AVAudioFileのクラス定義をみてみます。
open class AVAudioFile : NSObject {
public init(forReading fileURL: URL) throws
public init(forReading fileURL: URL, commonFormat format: AVAudioCommonFormat, interleaved: Bool) throws
public init(forWriting fileURL: URL, settings: [String : Any]) throws
public init(forWriting fileURL: URL, settings: [String : Any], commonFormat format: AVAudioCommonFormat, interleaved: Bool) throws
open func read(into buffer: AVAudioPCMBuffer) throws
open func read(into buffer: AVAudioPCMBuffer, frameCount frames: AVAudioFrameCount) throws
open func write(from buffer: AVAudioPCMBuffer) throws
open var url: URL { get }
open var fileFormat: AVAudioFormat { get }
open var processingFormat: AVAudioFormat { get }
open var length: AVAudioFramePosition { get }
open var framePosition: AVAudioFramePosition
}
forWritingというのを使いwriteを呼び出してクラスのメモリを解放すれば良さそうです。
AVAudioPCMBufferの定義を見て見ます。
open class AVAudioPCMBuffer : AVAudioBuffer {
public init(pcmFormat format: AVAudioFormat, frameCapacity: AVAudioFrameCount)
open var frameCapacity: AVAudioFrameCount { get }
open var frameLength: AVAudioFrameCount
open var stride: Int { get }
open var floatChannelData: UnsafePointer<UnsafeMutablePointer<Float>>? { get }
open var int16ChannelData: UnsafePointer<UnsafeMutablePointer<Int16>>? { get }
open var int32ChannelData: UnsafePointer<UnsafeMutablePointer<Int32>>? { get }
}
フォーマットとキャパシティ(=サンプル数)を指定してインスタンスを作り、データを詰めれば良さそうです。
書き込むフォーマットはWindowsで標準的なInt16、サンプルレート44100,チャネル数2,インターリーブであるWAVファイル形式とします。
書き込むデータはLには440Hzのサイン波、Rには523Hzのサイン波を1024サンプルだけ出力することにします。
音の長さは約0.023秒となります。
以下、実装です。
if let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
let path = documentsPath.appending("/test.wav")
let format:AVAudioFormat = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatInt16,
sampleRate: 44100,
channels: 2,
interleaved: true)
if let url = URL(string:path) {
do {
let file:AVAudioFile = try AVAudioFile(forWriting: url,
settings: format.settings,
commonFormat: format.commonFormat,
interleaved: true)
let buffer:AVAudioPCMBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: 1024)
if let p:UnsafePointer<UnsafeMutablePointer<Int16>> = buffer.int16ChannelData {
let q:UnsafeMutablePointer<Int16> = p.pointee
var x:Float = 0
var y:Float = 0
let delta_x:Float = Float(440 * 2 * M_PI / 44100)
let delta_y:Float = Float(523 * 2 * M_PI / 44100)
for i:Int in stride(from:0, to:1024*2, by: 2) {
q[i] = Int16(sin(x) * 32767.0)
q[i+1] = Int16(sin(y) * 32767.0)
x += delta_x
y += delta_y
}
} else {
assert(false)
}
buffer.frameLength = 1024
try file.write(from: buffer)
} catch {
assert(false)
}
}
}
ここでは、WAVファイルのフォーマットとデータを書き込む時のフォーマットを同一にしています。
同じにしておくとコンバートが走らないので効率が良いですが、異なるフォーマットでもある程度の範囲内なら
AVAudioFileが勝手にコンバートしてくれたりします。
経験的にはInterleavedやチャネル数が同じでないと動かないです。
interleaved:trueにした場合AVAudioPCMBufferのInt16の領域が1つになりますのでint16ChannelDataから
サンプル数分LRの順序で詰めていけば良いです。
buffer.frameLengthに書き込む値を毎回セットする必要があります。
AudioToolKitで書くよりソースコードが長くなっている気もしますが、
swiftは型安全性を何より優先しますので、こんな感じになります。
ついでに波形画像も載せておきます。
GitHub - pebble8888/AudioFileCreateSample