Audio Unit によるリアルタイムにタイムストレッチを行う方法を説明していきます。
タイムストレッチとは、音程を保ったまま再生速度を変える処理です。AUGraph(Audio Unit を複数接続するサービス)に含まれる機能を用いて、リアルタイム・タイムストレッチを実現することができます。
以下のアプリで実現していますので、よかったら試してみてください。
Time Stretcher - Realtime Time Stretch
今回は準備編ということで、オーディオファイルを読み込み、再生するまでの処理についてまとめます。
まず、クラスを定義します。
@interface AudioIO : NSObject { ExtAudioFileRef _extAudioFile; AudioStreamBasicDescription _outputFormat; UInt32 _numberOfChannels; SInt64 _totalFrames; SInt64 _currentFrame; AUGraph _graph; AudioUnit _remoteIOUnit; AudioUnit _converterUnit; AudioUnit _aUiPodTimeUnit; }
オーディオファイルの準備を行います。指定したオーディオファイルのファイルフォーマットを _outputFormat に読み込ませています。
AudioStreamBasicDescription AUCanonicalASBD(Float64 sampleRate, UInt32 channel) { AudioStreamBasicDescription audioFormat; audioFormat.mSampleRate = sampleRate; audioFormat.mFormatID = kAudioFormatLinearPCM; // audioFormat.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical; // CA_CANONICAL_DEPRECATED audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; audioFormat.mChannelsPerFrame = channel; audioFormat.mBytesPerPacket = sizeof(Float32); audioFormat.mBytesPerFrame = sizeof(Float32); audioFormat.mFramesPerPacket = 1; audioFormat.mBitsPerChannel = 8 * sizeof(Float32); audioFormat.mReserved = 0; return audioFormat; } - (SInt64)prepareAudioFile:(NSURL*)fileURL { // ExAudioFileの作成 ExtAudioFileOpenURL((CFURLRef)fileURL, &_extAudioFile); // ファイルフォーマットを取得 AudioStreamBasicDescription inputFormat; UInt32 size = sizeof(AudioStreamBasicDescription); ExtAudioFileGetProperty(_extAudioFile, kExtAudioFileProperty_FileDataFormat, &size, &inputFormat); // Audio Unit正準形のASBDにサンプリングレート、チャンネル数を設定 _numberOfChannels = inputFormat.mChannelsPerFrame; _outputFormat = AUCanonicalASBD(inputFormat.mSampleRate, inputFormat.mChannelsPerFrame); // 読み込むフォーマットをAudio Unit正準形に設定 ExtAudioFileSetProperty(_extAudioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &_outputFormat); // トータルフレーム数を取得しておく SInt64 fileLengthFrames = 0; size = sizeof(SInt64); ExtAudioFileGetProperty(_extAudioFile, kExtAudioFileProperty_FileLengthFrames, &size, &fileLengthFrames); _totalFrames = fileLengthFrames; // 位置を0に移動 ExtAudioFileSeek(_extAudioFile, 0); _currentFrame = 0; return fileLengthFrames; }
上記で読み込んだオーディオフォーマットを AUGraph で再生します。
簡単のために、まずは単純にオーディオファイルを普通に再生する手順を以下に示します。
- (OSStatus)prepareAUGraph { OSStatus ret = noErr; // 1. AUGraphの準備 NewAUGraph(&_graph); AUGraphOpen(_graph); // 2. AUNodeの作成 AudioComponentDescription cd; cd.componentType = kAudioUnitType_FormatConverter; cd.componentSubType = kAudioUnitSubType_AUConverter; cd.componentManufacturer = kAudioUnitManufacturer_Apple; cd.componentFlags = 0; cd.componentFlagsMask = 0; AUNode converterNode; AUGraphAddNode(_graph, &cd, &converterNode); AUGraphNodeInfo(_graph, converterNode, NULL, &_converterUnit); cd.componentType = kAudioUnitType_Output; cd.componentSubType = kAudioUnitSubType_RemoteIO; cd.componentManufacturer = kAudioUnitManufacturer_Apple; cd.componentFlags = 0; cd.componentFlagsMask = 0; AUNode remoteIONode; AUGraphAddNode(_graph, &cd, &remoteIONode); AUGraphNodeInfo(_graph, remoteIONode, NULL, &_remoteIOUnit); // 3. Callbackの作成 AURenderCallbackStruct callbackStruct; callbackStruct.inputProc = renderCallback; callbackStruct.inputProcRefCon = self; AUGraphSetNodeInputCallback(_graph, converterNode, 0, // bus number &callbackStruct); // 4. 各NodeをつなぐためのASBDの設定 AudioStreamBasicDescription asbd = {0}; UInt32 size = sizeof(asbd); // converter IO AudioUnitSetProperty(_converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &_outputFormat, size); AudioUnitSetProperty(_converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &_outputFormat, size); // remoteIO I AudioUnitSetProperty(_remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &_outputFormat, size); // 5. Nodeの接続 // AUConverter -> Remote IO AUGraphConnectNodeInput(_graph, converterNode, 0, remoteIONode, 0); // 6. AUGraphを初期化 AUGraphInitialize(_graph); return ret; }
上記では簡略化のために省略していますが、AUGraph の関数を用いる際は、戻り値のチェックを逐次行うことをお勧めします。正しく定義できていない場合、AudioUnitSetProperty や AUGraphInitialize でエラーとなることが経験上多いです。
上記で定義したコールバックの実装は以下のようになります。再生時にくり返し呼び出されます。
OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { OSStatus err = noErr; AudioIO *def = (AudioIO *)inRefCon; UInt32 ioNumberFrames = inNumberFrames; err = ExtAudioFileRead(def.extAudioFile, &ioNumberFrames, ioData); return err; }
再生/停止を行います。
- (void)start { if (_graph) { Boolean isRunning = false; OSStatus ret = AUGraphIsRunning(_graph, &isRunning); if (ret == noErr && !isRunning) { AUGraphStart(_graph); } } } - (void)stop { if (_graph) { Boolean isRunning = false; OSStatus ret = AUGraphIsRunning(_graph, &isRunning); if (ret == noErr && isRunning) { AUGraphStop(_graph); } } }
アプリ終了時は、以下の解放処理を行います。
- (void)releaseAUGraph { [self stop]; if(_graph != NULL) { AUGraphUninitialize(_graph); AUGraphClose(_graph); DisposeAUGraph(_graph); _graph = NULL; } } - (void)releaseAudioFile { if (_extAudioFile != NULL) { ExtAudioFileDispose(_extAudioFile); } }
次回はリアルタイム・タイムストレッチの場合の実装について説明を行います。