26
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-07-21

#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)

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

26
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?