やりたいこと
SwiftとAudioUnitを使ってマイクから入力した音声をスピーカー(とかイヤホンとか)からリアルタイムに出力できるようにします。
結果
- ほぼ、遅れのない音声の入出力ができました。
解説
- ぶっちゃけ、よくわかっていないので、あまり解説できませんが。。。
-
LooperModel
に全てのコードが詰め込んであります。
AudioUnitの準備
-
AudioUnit
を準備します。 - 以下のコードで
AudioUnit
を初期化します。- 詳しい方、解説ください。
audioUnit = AudioUnit()
var acd = AudioComponentDescription();
acd.componentType = kAudioUnitType_Output;
acd.componentSubType = kAudioUnitSubType_RemoteIO;
acd.componentManufacturer = kAudioUnitManufacturer_Apple;
acd.componentFlags = 0;
acd.componentFlagsMask = 0;
let ac = AudioComponentFindNext(nil, &acd);
AudioComponentInstanceNew( ac, &audioUnit! );
// ..... 様々な設定をする
AudioUnitInitialize( audioUnit! );
Callbackの設定
- AudioUnitでは定期的にコールバックが呼び出され、そのコールバック関数内で処理を行うことで、音声を入力したり出力したりすることができるようになっています。
- マイクからの音声入力を受け取り、バッファに保存するコールバックとバッファに保存しておいた音声を出力するコールバックを設定します。
// コールバックを設定するための構造体
// inputProc: コールバック関数の関数ポインタ(?)
// inputProcRefCon: コールバック関数に渡したいデータ
var input = AURenderCallbackStruct( inputProc: RecordingCallback, inputProcRefCon: &audioUnit );
// InputCallback(音声入力を受け取るコールバック)の設定
AudioUnitSetProperty(audioUnit!,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
kInputBus,
&input,
UInt32(sizeofValue(input)));
// RenderCallback(音声出力するコールバック)の設定
input.inputProc = RenderCallback;
AudioUnitSetProperty(audioUnit!,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
kOutputBus,
&input,
UInt32(sizeofValue(input)));
マイク入力、スピーカー出力を有効化する
- 以下のコードで入力、出力を有効化することができます。
var flag: UInt32 = 1;
AudioUnitSetProperty(audioUnit!,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
kInputBus,
&flag,
UInt32( sizeof( UInt32 ) ) );
AudioUnitSetProperty(audioUnit!,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
kOutputBus,
&flag,
UInt32( sizeof( UInt32 ) ) );
フォーマット形式を決定する
- よくわかってません。。。
- 詳しい方教えてください。
- 扱う音声の形式はこうだよ、という設定をしているようです。
var audioFormat: AudioStreamBasicDescription = AudioStreamBasicDescription();
audioFormat.mSampleRate = 44100.00;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
AudioUnitSetProperty( audioUnit!,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
kInputBus,
&audioFormat,
UInt32( sizeof( AudioStreamBasicDescription ) ) );
AudioUnitSetProperty( audioUnit!,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
kOutputBus,
&audioFormat,
UInt32( sizeof( AudioStreamBasicDescription ) ) );
バッファを確保するかどうかを設定する
- バッファを確保するかどうかを設定しているらしいです。
-
0
を設定しているのでバッファは確保しない。 - なんのバッファかはよくわかりません。
flag = 0;
AudioUnitSetProperty( audioUnit!,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
kInputBus,
&flag,
UInt32( sizeof( UInt32 ) ) );
AudioUnitの動作開始、停止を行う
- 以下のコードを呼ぶことで、開始、停止を行うことができます。
- 開始すると、コールバックに指定した関数が定期的に呼び出されるようになります。
AudioOutputUnitStart( audioUnit! ); // 開始
AudioOutputUnitStop( audioUnit! ); // 停止
マイクからの入力音声を受け取るコールバック
- コールバックの形式は以下のとおりです。
ioData
には何も入ってきません。(マイク入力音声がここに入るのかと思っていたのですが、どうやら違うようです。なんのためにいるんだろう。) - 実際のマイクからの音声は
AudioUnitRender()
を使います。第一引数にはAudioUnit
を渡す必要があります。 - 第六引数に入力音声データがコピーされて返ってきます。このデータをどうにかして音声出力のコールバック関数に渡せれば勝ちです。
- 第六引数に渡すためのバッファは自力で用意する必要があります。
- 用意すべきバッファサイズは
AudioStreamBasicDescription
で定義した何かが関係している気がします。 - 詳しい方、教えてください。
- 用意すべきバッファサイズは
func RecordingCallback(
inRefCon: UnsafeMutablePointer<Void>,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBusNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>) -> (OSStatus)
{
var err :OSStatus? = nil
let buffer = allocateAudioBuffer(1 ,size: inNumberFrames)
bufs = AudioBufferList.init(mNumberBuffers: 1, mBuffers: buffer)
let au = UnsafeMutablePointer<AudioUnit>(inRefCon).memory;
err = AudioUnitRender(au,
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
&bufs!)
return err!
}
func allocateAudioBuffer(let numChannel: UInt32, let size: UInt32) -> AudioBuffer {
let dataSize = UInt32(numChannel * UInt32(sizeof(Float64)) * size)
let data = malloc(Int(dataSize))
let buffer = AudioBuffer.init(mNumberChannels: numChannel, mDataByteSize: dataSize, mData: data)
return buffer
}
音声を出力するコールバック
- 入力された音声を出力するためのコールバック関数です。
- 今回は、入力された音声データを強引にグローバル変数に確保したバッファで渡しています。
- たぶん、inRefConを使ってデータの受け渡しをするのがスマートなんだろうなーと思います。
func RenderCallback (
inRefCon: UnsafeMutablePointer<Void>,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBusNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>) -> (OSStatus)
{
memcpy( ioData.memory.mBuffers.mData , &bufs!.mBuffers.mData, Int( 1 * inNumberFrames * UInt32( sizeof( Float64 ) ) ) );
let data=UnsafePointer<Int16>(bufs!.mBuffers.mData)
let dataArray = UnsafeBufferPointer<Int16>(start:data, count: Int(bufs!.mBuffers.mDataByteSize)/sizeof(Int16))
let io = UnsafeMutablePointer<Int16>( ioData.memory.mBuffers.mData );
let ioDataArr = UnsafeMutableBufferPointer<Int16>(start: io, count: Int(bufs!.mBuffers.mDataByteSize)/sizeof(Int16));
for i in 0...dataArray.count-1
{
ioDataArr[ i ] = dataArray[ i ];
}
bufs?.mBuffers.mData.dealloc(1);
return noErr;
}
参考
http://qiita.com/syuhei1985/items/4404f1360863ba8a06ac
http://atastypixel.com/blog/using-remoteio-audio-unit/