Core Audio ClockをMTCのスレーブにした時、あるいはインターナルのソースをHostTimeにした時、Core Audio Clockの時間を基準にしてオーディオを再生しようとすると、オーディオデバイスとスピードのずれが出てきますので、調整しなければいけません。
まず、オーディオの再生をしているときのIOProc内で、再生する頭のタイミングのHostTime(以下スタートホストタイム)と、HostTimeとオーディオデバイスのスピードの比率である「RateScalar」が取得できます。それと、MTCのスレーブにしているときはCoreAudioClockの「PlayRate」が、MTCとHostTimeのスピードの比率になっていますので、基本的にこれらを使って調整してみます。
1回のIOProcごとのオーディオデータを用意するスタート時間としてスタートホストタイムを設定し、オーディオデータに( RateScalar × PlayRate )で求めたレートでVariSpeed等をかけてやれば良いはずなのですが、実際はスタートホストタイムがアバウトで微妙にデバイスとずれていたりして音を鳴らすとプチプチとノイズが入ってしまったりします。
なので、スタートホストタイムは直接使わずに、( RateScalar × PlayRate )で調整したバッファフレームサイズ分の時間を毎度足していった値をスタート時間にして、スタートホストタイムとの差を少しずつ調整していくという感じでやってみました。
ということで、インスタンス変数には以下のようなものがあるとしておいて、
CAClockRef clockRef; //Core Audio Clock Float64 deviceSampleRate; //オーディオデバイスのサンプリング周波数 UInt32 bufferFrames; //オーディオデバイスのバッファフレーム数 double preSeconds; //スタート時間の差を求めるための時間
IOProc内で以下のようなメソッドを呼ぶという感じで試してみました。ここでは、オーディオデータのスタート時間と1フレーム分の時間を求めています。
- (void)ioProc:(AudioBufferList *)outOutputData
outputTime:(const AudioTimeStamp *)inOutputTime
{
OSStatus err;
//アウトプットデバイスのRateScalarを取得する
double rateScalar = inOutputTime->mRateScalar;
//スピード調整のためのレート
double ajustRate;
//今回のスタート時間
double startSeconds;
//CAClockのプレイレートを取得する
double playRate;
err = CAClockGetPlayRate(clockRef, &playRate);
if (err != noErr) NSLog(@"get PlayRate Err");
//アウトプットデバイスのスタート時間をホストタイムから変換して取得する
CAClockTime inHostTime;
inHostTime.format = kCAClockTimeFormat_HostTime;
inHostTime.time.hostTime = inOutputTime->mHostTime;
CAClockTime outSecondsTime;
outSecondsTime.format = kCAClockTimeFormat_Seconds;
err = CAClockTranslateTime(clockRef, &inHostTime,
kCAClockTimeFormat_Seconds, &outSecondsTime);
if (err != noErr) NSLog(@"translatetime Err");
//前回コールバック時に求めたスタート時間との差
double gap = outSecondsTime.time.seconds - preSeconds;
//今回のスタート時間を設定する。前回との差が大きければリセット
if (fabs(gap) > 0.1) {
startSeconds = outSecondsTime.time.seconds;
gap = 0.0;
} else {
startSeconds = preSeconds;
}
//差がきわめて小さければ調整はしない。
//調整するなら差の大きさによって調整するレートを求める
if (fabs(gap) > 0.0001) {
ajustRate = pow(10, gap);
} else {
ajustRate = 1.0;
}
//1サンプル分の時間
double secondsPerFrame =
playRate * rateScalar / deviceSampleRate * ajustRate;
//次のコールバック時のスタート時間を算出する
preSeconds = startSeconds + secondsPerFrame * bufferFrames;
//
// ここで、startSecondsとsecondsPerFrameを元に
// オーディオデータを書き込む
//
}