とりあえず自作エフェクトユニットで入力したデータをそのまま出力に流せるようになりました。
なお、確認用ホストアプリとしてAudioUnitV3Example: A Basic AudioUnit Extension and Host Implementationに入っているAUv3Host をビルドして使ってます。
テンプレートターゲットそのままだと、Auv3Hostで
AVAudioNode.mm:747: AUSetFormat: error -10877というエラーが出て動きませんでした。
どうやら自作AudioUnitクラス(MyAudioUnit)で最低限inputBussesとoutputBussesをオーバーライドする必要があるみたいです。
とりあえず、AudioUnitv3ExampleのFilterDemoを参考に
としました。- (AUAudioUnitBusArray *)inputBusses {AUAudioUnitBusArray *buses = [[AUAudioUnitBusArray alloc] initWithAudioUnit:selfbusType:AUAudioUnitBusTypeInputbusses:@[self.inputBus]];return buses;}- (AUAudioUnitBusArray *)outputBusses {AUAudioUnitBusArray *buses = [[AUAudioUnitBusArray alloc] initWithAudioUnit:selfbusType:AUAudioUnitBusTypeOutputbusses:@[self.outputBus]];return buses;}
なお、inputBus, outputBusは
と定義しており、initWithComponentDescription:options:error:内で@property (nonatomic) AUAudioUnitBus *inputBus;@property (nonatomic) AUAudioUnitBus *outputBus;
と初期化しています。const UInt32 numCh = 2;AVAudioFormat *defaultFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100.0 channels:numCh];NSError *error = nil;self.inputBus = [[AUAudioUnitBus alloc] initWithFormat:defaultFormaterror:&error];if (error) {NSLog(@"failed to make input bus. error:%@", error);return nil;}self.outputBus = [[AUAudioUnitBus alloc] initWithFormat:defaultFormaterror:&error];if (error) {NSLog(@"failed to make output bus. error:%@", error);return nil;}
ひとまずこれでAUv3Hostはエラーを吐かなくなりました。
が、音が全く出てきませんでした。
次はinternalRenderBlockをオーバーライドして、出力バッファにデータをセットする必要があるようです。
スルーさせるだけなので、最初は
としてみたんですが、期待どおり動きませんでした。AUAudioUnitStatus status = pullInputBlock(actionFlags, timestamp, frameCount, 0, outputData);
なぜ上手くいかないのか未だに理解できてません。
(デバッグしにくいのが難点)
この場合もFilterDemoを参考に
としました。- (AUInternalRenderBlock)internalRenderBlock {return ^AUAudioUnitStatus(AudioUnitRenderActionFlags *actionFlags, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList *outputData, const AURenderEvent *realtimeEventListHead, AURenderPullInputBlock pullInputBlock) {AUAudioUnitStatus status = pullInputBlock(actionFlags, timestamp, frameCount, 0, self.list);if (status != noErr)return status;for (UInt32 b = 0; b < outputData->mNumberBuffers; b++) {if (outputData->mBuffers[b].mDataByteSize < sizeof(float) * frameCount) {NSLog(@"invalid output data size");return kAudioUnitErr_TooManyFramesToProcess;}if (self.list->mBuffers[b].mDataByteSize < sizeof(float) * frameCount) {NSLog(@"invalid input data size");return kAudioUnitErr_TooManyFramesToProcess;}memcpy(outputData->mBuffers[b].mData, self.list->mBuffers[0].mData, sizeof(float) * frameCount);}return noErr;};}
listの定義は
で、こちらもinitWithComponentDescription:options:error:内で@property (nonatomic) AudioBufferList *list;
と初期化しています。要はバッファ領域を作ってるだけですね。self.list = malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (numCh - 1));const UInt32 numMaxFrames = 1024;for (UInt32 ch = 0; ch < numCh; ch++) {self.list->mNumberBuffers = numCh;self.list->mBuffers[ch].mNumberChannels = 1;self.list->mBuffers[ch].mDataByteSize = numMaxFrames * sizeof(float);self.list->mBuffers[ch].mData = malloc(numMaxFrames * sizeof(float));}
チャンネル数 - 1だけAudioBuffer分を余計に取っているのと、
float決め打ちでバッファを作っているのは入出力バスのフォーマットとして
デフォルトのもの(正準形, non-interleaved, float)を指定しているためです。
とりあえずこれで入力された音を出力できるようになりました。
ただし、allocateRenderResourcesAndReturnError:とdeallocateRenderResourcesを
どういう用途で使えば良いのかいまいちわかりませんでした。
init...とdeallocでやってはいけないんだろうか…。
その辺も含めてFilterDemoを調べていきたいと思います。