月別アーカイブ: 2013年10月

Audio Unit によるリアルタイム・タイムストレッチ(3. パラメータ設定)

本タイトルでは最後となる、タイムストレッチのパラメータ設定について説明していきます。
まず、前回説明した prepareAUGraph 関数内でコメントにしていた箇所を有効にします。

- (OSStatus)prepareAUGraph
{
	
	// (略)
	
	// 6. AUiPodTimeOtherへ設定するためのプロキシオブジェクトを作成
	_aUiPodTimeProxy = [[AUiPodTimeProxy alloc] initWithReverbUnit:_aUiPodTimeUnit];
	
	
	// (略)
	
}

上記で生成している AUiPodTimeProxy クラスでタイムストレッチのパラメータ設定を行います。

まず、AUiPodTimeProxy クラスの定義を示します。
初期化関数として、AudioUnit のパラメータを引数として必要とするため、自作の initWithAudioUnit 関数を用意しています。

@interface AUiPodTimeProxy : NSObject
{
    AudioUnit _aUiPodTimeUnit;
	
	Float32 _playbackRate;
	
	AudioUnitParameterID _paramId;
}

@property(atomic) Float32 playbackRate;

- (id)initWithAudioUnit:(AudioUnit)aUiPodTimeUnit;

@end

実装部分は以下のようになります。

@implementation AUiPodTimeProxy

@synthesize playbackRate = _playbackRate;

- (id)initWithAudioUnit:(AudioUnit)aUiPodTimeUnit
{
    self = [super init];
    if (self) {
        _aUiPodTimeUnit = aUiPodTimeUnit;
		_playbackRate = self.playbackRate;
		
		UInt32 size = sizeof(UInt32);
		AudioUnitGetPropertyInfo(_aUiPodTimeUnit,
								 kAudioUnitProperty_ParameterList,
								 kAudioUnitScope_Global,
								 0,
								 &size,
								 NULL);
		
		int numOfParams = size / sizeof(AudioUnitParameterID);
		AudioUnitParameterID paramList[numOfParams];
		
		AudioUnitGetProperty(_aUiPodTimeUnit,
							 kAudioUnitProperty_ParameterList,
							 kAudioUnitScope_Global,
							 0,
							 paramList,
							 &size);
		
		for (int i = 0; i < numOfParams; i++) {
			_paramId = paramList[i];
			
			AudioUnitParameterInfo paramInfo;
			size = sizeof(paramInfo);
			AudioUnitGetProperty(_aUiPodTimeUnit,
								 kAudioUnitProperty_ParameterInfo,
								 kAudioUnitScope_Global,
								 paramList[i],
								 &paramInfo,
								 &size);
			
			AudioUnitSetParameter(_aUiPodTimeUnit,
								  paramList[i],
								  kAudioUnitScope_Global,
								  0,
								  _playbackRate,
								  0);
		}
    }
    return self;
}

- (Float32)playbackRate
{
	Float32 value = 0.0f;
	OSStatus ret = AudioUnitGetParameter(_aUiPodTimeUnit,
										 _paramId,
										 kAudioUnitScope_Global,
										 0,
										 &value);
	if (ret != noErr) {
		NSLog(@"Error getting parameter(%d)", parameter);
	}
	return value;
}
- (void)setPlaybackRate:(Float32)value
{
	// AUiPodTimeOther の場合
	int parameter = 0;
	Float32 min = 0.5f;
	Float32 max = 2.0f;
	
	if (value < min || value > max) {
		NSLog(@"Invalid value(%f)<%f - %f> for parameter(%d). Ignored.", value, min, max, parameter);
		return;
	}
	
	OSStatus ret = AudioUnitSetParameter(_aUiPodTimeUnit,
										 parameter,
										 kAudioUnitScope_Global,
										 0,
										 value,
										 0);
	if (ret != noErr) {
		NSLog(@"Error setting parameter(%d)", parameter);
	}
}

@end

initWithAudioUnit 関数について説明します。
AudioUnitGetPropertyInfo で、AudioUnitGetProperty で取得する paramList のサイズを取得します。
次の AudioUnitGetProperty(第2引数を kAudioUnitProperty_ParameterList)で paramList の各IDを取得します。
for文内の AudioUnitGetProperty(第2引数を kAudioUnitProperty_ParameterInfo)で各IDのパラメータが取得できます。

タイムストレッチ(AUiPodTimeOther)の場合、取得する値は以下のようになります。

 numOfParams = 1
 paramList[0] = 0
 paramInfo.name = rate
 paramInfo.minValue = 0.5
 paramInfo.maxValue = 2.0
 paramInfo.defaultValue = 1.0

最後の AudioUnitSetParameter で、上記の値を設定します。

playbackRate プロパティを用いて、タイムストレッチの値を取得/設定することができます。
スライダー(UISlider)などを用いてコントロールすると良いと思います。

さすがAppleが用意している機能だけあって、リアルタイムでもスムーズにタイムストレッチをしてくれるので気持ちいいです。よかったら試してみてください。

Audio Unit によるリアルタイム・タイムストレッチ(2. タイムストレッチ)

今回はタイムストレッチの処理を行う実装の説明を行います。
前回の prepareAUGraph 関数を追記/修正することになります。

「2. AUNodeの作成」において、componentSubType が kAudioUnitSubType_AUiPodTimeOther の AUNode を追加することで、タイムストレッチの処理を行うことができます。

「5. Nodeの接続」は以下のようになります。
AUConverter -> AUiPodTimeOther -> Remote IO

- (OSStatus)prepareAUGraph
{
	// 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_FormatConverter;
	cd.componentSubType = kAudioUnitSubType_AUiPodTimeOther;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode aUiPodTimeNode;
	AUGraphAddNode(_graph, &cd, &aUiPodTimeNode);
	AUGraphNodeInfo(_graph, aUiPodTimeNode, NULL, &_aUiPodTimeUnit);
	
	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);
	
	// aUiPodTime IO
	AudioUnitSetProperty(_aUiPodTimeUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Output, 0,
						 &_outputFormat, size);
	
	AudioUnitSetProperty(_aUiPodTimeUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	// remoteIO I
	AudioUnitSetProperty(_remoteIOUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	// 5. Nodeの接続
	// AUConverter -> AUiPodTimeOther
	AUGraphConnectNodeInput(_graph,
							converterNode, 0,
							aUiPodTimeNode, 0);
	
	// AUiPodTimeOther -> Remote IO
	AUGraphConnectNodeInput(_graph,
							aUiPodTimeNode, 0,
							remoteIONode, 0);
	
	// 6. AUiPodTimeOtherへ設定するためのプロキシオブジェクトを作成
	// (次回に説明するため、今回はコメント)
//	_aUiPodTimeProxy = [[AUiPodTimeProxy alloc] initWithReverbUnit:_aUiPodTimeUnit];
	
	// 7. AUGraphを初期化
	AUGraphInitialize(_graph);
	
	return ret;
}

タイムストレッチの値を変更するための処理は、上記でコメントしている箇所にて行うことになります。説明は次回に行います。

(追記:2015/03/23)
現時点での最新OS(iOS 8)で確認したところ、kAudioUnitSubType_AUiPodTimeOther についてもFloat32型での定義扱いになっているようです。(最初に本記事を書いた頃はそうではなかったのですが)
そのため、上記はそのまま使うと実行時エラーになると思います。
修正方法は、「イコライザーを追加しました(Tune Changer)」を参考にしていただければと思います。

Audio Unit によるリアルタイム・タイムストレッチ(1. 準備)

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);
	}
}

次回はリアルタイム・タイムストレッチの場合の実装について説明を行います。