2011-07-26 12 views
1

私のアプリの中には、1分間に指定した回数(bpm)のクリック音を鳴らす簡単なメトロノームスタイルの機能があります。私は、指定されたbpmから計算された間隔で、サウンドを再生するメソッドを呼び出すNSTimerを起動することでこれを行います。私のオーディオ・サウンドが時間通りに再生されないのはなぜですか?

私がNSLog行をplayメソッドに入れると、NSTimerが約1ミリ秒に正確に起動していることがわかります。しかし、音声出力をオーディオエディタに記録してからクリック間隔を測定すると、それらが均等に配置されていないことがわかります。たとえば、150 bpmの場合、タイマーは400ミリ秒ごとに起動します。しかし、ほとんどのサウンドは395ミリ秒後に再生され、3番目または4番目のサウンドは418ミリ秒後に再生されます。

音が一様に遅れているわけではなく、より短く長い間隔のパターンに従います。 iOSのサウンドのタイミングの解像度が低いようで、各サウンドイベントを最も近い利用可能なポイントに丸め、トラック全体を維持するために必要に応じて切り上げまたは切り下げます。

私はこれをシステムサウンド、AVAudioPlayer、OpenALで試してみたところ、3つの方法すべてで全く同じ結果が得られました。それぞれの方法では、ビューが読み込まれるときにすべての設定を行っています。そのため、再生するたびに再生する必要があります。 AVAudioPlayerを使用して、サウンドが再生されるたびに2番目のタイマーを使用してprepareToPlayを呼び出してみました。そのため、次回には初期化され準備が整いますが、同じ結果が得られました。ここで

は、のviewDidLoad(this tutorialから適応)にOpenALの音を設定するためのコードです:

// set up the context and device 
ALCcontext *context; 
ALCdevice *device; 
OSStatus result; 
device = alcOpenDevice(NULL); // select the "preferred device" 
if (device) { 
    context = alcCreateContext(device, NULL); // use the device to make a context 
    alcMakeContextCurrent(context); // set the context to the currently active one 
} 

// open the sound file 
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:@"TempoClick" ofType:@"caf"]; 
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath]; 
AudioFileID fileID; 
result = AudioFileOpenURL((CFURLRef)soundFileURL, kAudioFileReadPermission, 0, &fileID); 
if (result != 0) DLog(@"cannot open file %@: %ld", soundFilePath, result); 

// get the size of the file data 
UInt32 fileSize = 0; 
UInt32 propSize = sizeof(UInt64); 
result = AudioFileGetProperty(fileID, kAudioFilePropertyAudioDataByteCount, &propSize, &fileSize); 
if (result != 0) DLog(@"cannot find file size: %ld", result); 
DLog(@"file size: %li", fileSize); 

// copy the data into a buffer, then close the file 
unsigned char *outData = malloc(fileSize); 
AudioFileOpenURL((CFURLRef)soundFileURL, kAudioFileReadPermission, 0, &fileID); // we get a "file is not open" error on the next line if we don't open this again 
result = AudioFileReadBytes(fileID, false, 0, &fileSize, outData); 
if (result != 0) NSLog(@"cannot load data: %ld", result); 
AudioFileClose(fileID); 
alGenBuffers(1, &tempoSoundBuffer); 
alBufferData(self.tempoSoundBuffer, AL_FORMAT_MONO16, outData, fileSize, 44100); 
free(outData); 
outData = NULL; 

// connect the buffer to the source and set some preferences 
alGenSources(1, &tempoSoundSource); 
alSourcei(tempoSoundSource, AL_BUFFER, tempoSoundBuffer); 
alSourcef(tempoSoundSource, AL_PITCH, 1.0f); 
alSourcef(tempoSoundSource, AL_GAIN, 1.0f); 
alSourcei(tempoSoundSource, AL_LOOPING, AL_FALSE); 

そして、私はちょうど呼び出しplayメソッドで:

alSourcePlay(self.tempoSoundSource); 

誰が何であるかを説明することができますここで起こって、私はそれを回避する方法は?

UPDATE 1:

私は、クリック音ごとに400ミリ秒を再生するには、そのプロジェクトにタイマーを追加した簡単なテストように私は、オーディオユニットと簡単なサウンドを再生別のプロジェクトを持っています。その場合、タイミングはほぼ完璧です。ですから、NSTimerはうまくいきますが、システムサウンド、AVAudioPlayerとOpenALはオーディオユニットよりも再生の精度が劣るようです。

UPDATE 2:

私はオーディオユニットを使用するように私のプロジェクトを再加工し、現在のオーディオは、はるかに正確に再生されます。どちらの方向にも4ミリ秒まで変動することがありますが、これは他のオーディオ方法よりも優れています。私はまだ興味があるのですが、他の方法はすべて短く、短い、短い、長い間隔のパターンを示しています - オーディオ再生時間が何らかの種類のフレームレートにマップされるように切り上げられているようですそれを説明したり、他のオーディオメソッドの回避策を提供できる人なら誰でもこの質問を開いておくことができます。

+0

あなたのNSLogが遅れを引き起こしているかもしれませんが、それがあれば – Ahmed

+0

@Ahmed:それは良い考えですが、私はNSLogをコメントアウトして同じ結果を得ました。 – arlomedia

+0

[iPhoneでリアルタイムの正確なオーディオシーケンサーをプログラミングするにはどうすればいいですか?](http://stackoverflow.com/questions/907137/how-to-program-a-real-time-accurate-audio-sequencer -on-the-iphone) – benzado

答えて

1

さて、私はそれを理解しました。

Float32 preferredBufferSize = .001; 
UInt32 size = sizeof(preferredBufferSize); 
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, size, &preferredBufferSize); 

ときI:オーディオユニットは、他のオーディオの方法よりも優れて働いていた本当の理由は、私は別のプロジェクトから適応された私のオーディオユニットクラスは、このように、オーディオセッションでバッファdurationプロパティを設定したことがありますこのコードをOpenALバージョンやAVAudioPlayerバージョンに加えても、オーディオユニットと同じように数ミリ秒以内に正確さが得られます。 (しかし、システムサウンドはそれほど正確ではありませんでした。)バッファサイズを増やして再生間隔を見るのがより正確でないことを確認することで、接続を確認できます。

もちろん、私はオーディオユニットを使用するプロジェクトに一日を費やした後にしか考えませんでした.C++でコンパイルしたり、割り込みハンドラをテストしたりしています。 。

1

NSTimerは、メソッドが実際に起動されることを保証しません。ここ

さらに詳しい情報:How to program a real-time accurate audio sequencer on the iphone?

編集内容について:

AVAudioPlayerは、それ自体を初期化するためにいくつかの時間がかかります。 prepareToPlayを呼び出すと、呼び出しを再生すると直ちに現在読み込まれているサウンドを再生できるように初期化されます。再生が停止すると、それ自体は初期化されないため、prepareToPlayを再度呼び出して再初期化する必要があります。離散的なサウンド再生ではなく、このクラスをstream-y再生に使用するのが最善です。

OpenALを使用すると、バッファを読み込んだ後にソースに接続して再生すると、遅延が発生しません。

オーディオユニットのコードを.mmファイルにカプセル化して、.mモジュールから呼び出すことができます。これらのコードは、C++としてコンパイルすることなくコンパイルすることができます。

+1

私のNSLogsのタイムスタンプによれば、この方法は最も近いミリ秒に発射されています。上記のリンクは、より正確なタイマーを作ることに焦点を当てていますが、私の場合、タイマーは問題ではないようです。 – arlomedia

+0

"オーディオユニットのコードを.mmファイルにカプセル化し、.mモジュールから呼び出すことができます。これはC++のようにコンパイルする必要はありません。あなたはそれについて確かですか?私はそれを働かせることができませんでした。明確にすることができれば、私はこれに関する別の質問を投稿しました:http://stackoverflow.com/questions/6944465/how-can-i-use-a-few-c-files-in-my-project-without -cファイルごとのコンパイル – arlomedia

関連する問題