#Swiftでオーディオ・音声分析への道 1 オーディオデータ読み込み、再生、書き出し
##はじめに
vDSP系ライブラリを使用したオーディオ・音声分析の記事をいくつか書いてから、随分と時間が経ちました。
当時はまだSwiftが世に出て日が浅かったのですが、今日ではVersionも2になり、(もうすぐ3が出ますが)、オーディオ処理のプログラムもSwiftで書きたい!という欲求のもと色々と試行錯誤してきました。
これを機に、今まで書いてきた 「オーディオ・音声分析への道」のSwift版をここにまとめておこうと思います。
1/3は自分が忘れないためのメモ、1/3は皆様に間違いの指摘や、新たな提案をしていただく機会を作ること、残り1/3はもし皆様のSwiftでの信号処理ライフに何か貢献できれば、大変幸せ、というのが執筆の動機です。
オーディオ・音声分析への道 その1
http://qiita.com/programanx1/items/3c3bb75c20c6dbb4328b
いずれにせよ、信号処理をやるなら、まずはオーディオデータを読み込まなければ何も始まりません。Swiftを使用するのであれば、やはりAppleのライブラリを積極的に使用していきたいです。
##AVFoundation
オーディオファイルの読み込み、書き出しについては、前回の記事でLibsndライブラリを使用していました。SwiftやObjective-Cを使用する場合、やはりApple純正のAVFoundation frameworkを使用するのが自然かと思います。
詳しくはAppleのReferenceを参照ください。
https://developer.apple.com/av-foundation/
この記事では、AudioDataの読み書きを扱うClassを作ることを目標に書いていきたいと思います。
*用途やデザインによって、クラスの設計は多様かと思います。この記事では、単純にオーディオデータの読み書きを行うクラスを独断と偏見で書いていきます。
##audioClass
audioClass.swiftを作ります。
クラス名はAudioDataClassにします。
import Foundation
import AVFoundation
class AudioDataClass{
}
AVFoundationを使用する場合は、AVFoundation Frameworksをimportします。
このクラスでは、
1.オーディオファイルのアドレスを引数として得る
2.オーディオファイルの読み込み
3.オーディオファイルのバイナリデータを抽出
4.チャンネル別のバイナリデータ(float)をbufferプロパティに格納する
ことを手順とします。
#Load Audio File
AVAudioFileクラスを使用します。
クラスのメンバとして、audioFile プロパティを追加します。
このプロパティは、読み込んだオーディオファイルの情報を格納し保持するものです。
// object for audio file
var audioFile:AVAudioFile!
クラスのイニシャライザで、オーディオファイルのアドレスを受け取るようにします。
import Foundation
import AVFoundation
class AudioDataClass{
//object for audio file
var audioFile:AVAudioFile!
//buffer for PCM data 便宜上AVAudioPCMBuffer型の変数を用意
//クラス外から実際にバイナリデータにアクセスする際はbufferプロパティを使う。
var PCMBuffer:AVAudioPCMBuffer!
// audio file address
var address:String
//オーディオのバイナリデータを格納するためのbuffer, マルチチャンネルに対応するため、二次元配列になっています。
var buffer:[[Float]]! = Array<Array<Float>>()
//オーディオデータの情報
var samplingRate:Double?
var nChannel:Int?
var nframe:Int?
//initializer
init(address:String){
self.address = address
}
}
次に、このクラスに、 **loadAudioData()**メソッドを追加します。
//import audio file
func loadAudioData(){
//create AVAudioFile
//error handling do catch
do{
//オーディオファイルを読み込み、データをaudioFileに格納
self.audioFile = try AVAudioFile(forReading: NSURL(fileURLWithPath: self.address))
//get samplingRate
self.samplingRate = self.audioFile.fileFormat.sampleRate
//get channel
self.nChannel = Int(self.audioFile.fileFormat.channelCount)
}catch{
//読み込み失敗
print("Error : loading audio file \(self.address) failed.")
}
//もしオーディオファイル読み込みが成功していたら、バイナリデータを取得する
if(self.audioFile != nil){
//get frame length
self.nframe = Int(self.audioFile.length)
//allocate
self.PCMBuffer = AVAudioPCMBuffer(PCMFormat: self.audioFile.processingFormat, frameCapacity: AVAudioFrameCount(self.nframe!))
//error handling
do{
//audioFileから、PCMBufferにバイナリデータを取得する
try self.audioFile.readIntoBuffer(self.PCMBuffer)
//各チャンネル毎にバイナリデータをbufferに追加する
for i in 0..<self.nChannel!{
let buf:[Float] = Array(UnsafeMutableBufferPointer(start:self.PCMBuffer.floatChannelData[i], count:self.nframe!))
self.buffer.append(buf)
}
}catch{
print("loading audio data failed.")
}
}
}
チャンネル毎のバイナリデータのアクセス法は、
for i in 0..<ChannelNumber{
for j in 0..<nframe{
print(self.buffer[i][j])
}
}
となります。
##Write Audio File
次は、オーディオファイルの書き出し方法です。
AudioDataClassに下記メソッドを追加します。
//arg1: data:[[Float]] : バイナリデータを保持した、多チャンネルに対応したバッファ。クラス内ではbufferプロパティに対応する。
//arg2: address : オーディオファイル書き出し先+ファイル名
//arg3: format : writeAudioData()メソッド使用前にloadAudioData()メソッドを使用してオーディオファイルを読み込んでいる場合、新たにformatを指定する必要はない。その場合はnilを渡す。
//もしバイナリデータのフォーマットを変えたい場合などはここに指定する。
func writeAudioData(data:[[Float]],address:String,format:AVAudioFormat?)->Bool{
//バイナリデータフォーマットを格納する
var audioformat:AVAudioFormat?
let nChannel:Int = data.count
let nframe:Int = data[0].count
//フレーム数が0の場合、dataは空。
if(nframe == 0){print("Error : no data."); return false}
//チャンネル数が0であれば、dataは空
if(nChannel > 0){
//読み込んだオーディオファイルと同じフバイナリフォーマットで書き出す場合
if(format == nil){ // we follow loaded audio file format
//サンプリングレートの設定がなければ、デフォルトの44100hzを採用
if(self.samplingRate == nil){self.samplingRate = 44100;}
if(self.audioFile != nil){
//setup audio format
audioformat = AVAudioFormat(standardFormatWithSampleRate: self.samplingRate!, channels: AVAudioChannelCount(nChannel))
}
}else{// we use new audio file format
audioformat = format
}
}else{
return false
}
//make PCMBuffer
let buffer = AVAudioPCMBuffer(PCMFormat:audioformat!, frameCapacity: AVAudioFrameCount(nframe))
//update frameLength which is the actual size of the file to be written in a disk
buffer.frameLength = AVAudioFrameCount(nframe)
//copy input data to PCMBuffer
for i in 0..<nChannel{
for j in 0..<nframe{
buffer.floatChannelData[i][j] = data[i][j]
}
}
//make an audio file for writing
var writeAudioFile:AVAudioFile?
do{
//書き出すオーディオファイルのフォーマット
writeAudioFile = try AVAudioFile(forWriting: NSURL(fileURLWithPath: address), settings: [
AVFormatIDKey:Int(kAudioFormatLinearPCM), // file format
AVSampleRateKey:audioformat!.sampleRate,
AVNumberOfChannelsKey:nChannel,
AVEncoderBitRatePerChannelKey:16 // 16bit
])
}catch{
print("Error : making audio file failed.")
return false
}
//export an audio file
do{
//書き出し
try writeAudioFile!.writeFromBuffer(buffer)
print("\(nframe) samples are written in \(address)")
}catch{
print("Error : Could not export audio file")
return false
}
return true
}
下記の部分で、AVFormatIDKeyにファイルフォーマットを設定します。
writeAudioFile = try AVAudioFile(forWriting: NSURL(fileURLWithPath: address), settings: [
AVFormatIDKey:Int(kAudioFormatLinearPCM), // file format
AVSampleRateKey:audioformat!.sampleRate,
AVNumberOfChannelsKey:nChannel,
AVEncoderBitRatePerChannelKey:16 // 16bit
])
フォーマットの種類は以下の通りです。
public var kAudioFormatLinearPCM: AudioFormatID { get }
public var kAudioFormatAC3: AudioFormatID { get }
public var kAudioFormat60958AC3: AudioFormatID { get }
public var kAudioFormatAppleIMA4: AudioFormatID { get }
public var kAudioFormatMPEG4AAC: AudioFormatID { get }
public var kAudioFormatMPEG4CELP: AudioFormatID { get }
public var kAudioFormatMPEG4HVXC: AudioFormatID { get }
public var kAudioFormatMPEG4TwinVQ: AudioFormatID { get }
public var kAudioFormatMACE3: AudioFormatID { get }
public var kAudioFormatMACE6: AudioFormatID { get }
public var kAudioFormatULaw: AudioFormatID { get }
public var kAudioFormatALaw: AudioFormatID { get }
public var kAudioFormatQDesign: AudioFormatID { get }
public var kAudioFormatQDesign2: AudioFormatID { get }
public var kAudioFormatQUALCOMM: AudioFormatID { get }
public var kAudioFormatMPEGLayer1: AudioFormatID { get }
public var kAudioFormatMPEGLayer2: AudioFormatID { get }
public var kAudioFormatMPEGLayer3: AudioFormatID { get }
public var kAudioFormatTimeCode: AudioFormatID { get }
public var kAudioFormatMIDIStream: AudioFormatID { get }
public var kAudioFormatParameterValueStream: AudioFormatID { get }
public var kAudioFormatAppleLossless: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_HE: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_LD: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_ELD: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_ELD_SBR: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_ELD_V2: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_HE_V2: AudioFormatID { get }
public var kAudioFormatMPEG4AAC_Spatial: AudioFormatID { get }
public var kAudioFormatAMR: AudioFormatID { get }
public var kAudioFormatAMR_WB: AudioFormatID { get }
public var kAudioFormatAudible: AudioFormatID { get }
public var kAudioFormatiLBC: AudioFormatID { get }
public var kAudioFormatDVIIntelIMA: AudioFormatID { get }
public var kAudioFormatMicrosoftGSM: AudioFormatID { get }
public var kAudioFormatAES3: AudioFormatID { get }
public var kAudioFormatEnhancedAC3: AudioFormatID { get }
##使い方
var audioClass = AudioDataClass(address:url)
//読み込み
audioClass.loadAudioData()
//書き出し
audioClass.writeAudioData(audioClass.buffer,address:url,format:nil)
##予告
次回は、様々なオーディオデータの再生方法をやります。