[AVFoundation][Swift] Swiftでオーディオ・音声分析への道 1 オーディオデータ読み込み、書き出し

#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**にします。

```switch.swift

import Foundation
import AVFoundation

class AudioDataClass{

}
```

AVFoundationを使用する場合は、AVFoundation Frameworksをimportします。
このクラスでは、

1.オーディオファイルのアドレスを引数として得る
2.オーディオファイルの読み込み
3.オーディオファイルのバイナリデータを抽出
4.チャンネル別のバイナリデータ(float)をbufferプロパティに格納する

ことを手順とします。

#Load Audio File

**AVAudioFile**クラスを使用します。

クラスのメンバとして、**audioFile** プロパティを追加します。
このプロパティは、読み込んだオーディオファイルの情報を格納し保持するものです。

```switch.swift

    // object for audio file
    var audioFile:AVAudioFile!

```

クラスのイニシャライザで、オーディオファイルのアドレスを受け取るようにします。

```class.swift


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()**メソッドを追加します。

```class.swift
//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.")
            } 
        }
    }
```

チャンネル毎のバイナリデータのアクセス法は、

```Binary.swift

for i in 0..<ChannelNumber{
     for j in 0..<nframe{
          print(self.buffer[i][j])
     }
}

```

となります。

##Write Audio File
次は、オーディオファイルの書き出し方法です。

**AudioDataClass**に下記メソッドを追加します。

```writeAudioData.swift

    //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**にファイルフォーマットを設定します。

```file.swift

            writeAudioFile = try AVAudioFile(forWriting: NSURL(fileURLWithPath: address), settings: [
                AVFormatIDKey:Int(kAudioFormatLinearPCM), // file format
                AVSampleRateKey:audioformat!.sampleRate,
                AVNumberOfChannelsKey:nChannel,
                AVEncoderBitRatePerChannelKey:16 // 16bit
                ])

```

フォーマットの種類は以下の通りです。

```file_format.swift

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 }

```


##使い方

```howToUse.swift

     var audioClass = AudioDataClass(address:url)
     //読み込み
     audioClass.loadAudioData()

     //書き出し
     audioClass.writeAudioData(audioClass.buffer,address:url,format:nil)

```

##予告
次回は、様々なオーディオデータの再生方法をやります。