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)
予告
次回は、様々なオーディオデータの再生方法をやります。