Example #1
0
double AudioStream::estimateActualSampleRate(const double sampleRate, MasterClockNanos &firstSampleMasterClockNanos, MasterClockNanos &lastSampleMasterClockNanos, const MasterClockNanos audioLatency, const quint32 frameCount) {
	MasterClockNanos newFirstSampleMasterClockNanos = firstSampleMasterClockNanos;
	// Ensure rendering time function has no breaks while no x-runs happen
	if (qAbs(firstSampleMasterClockNanos - lastSampleMasterClockNanos) < audioLatency) {
		firstSampleMasterClockNanos = lastSampleMasterClockNanos;
	}
	// Estimate rendering time using nominal sample rate
	MasterClockNanos nominalNanosToRender = MasterClockNanos(MasterClock::NANOS_PER_SECOND * frameCount / sampleRate);
	// Ensure outputBufferDacTime estimation doesn't go too far from expected.
	// Assume the real sample rate differs from nominal one no more than by 1%.
	// Actual hardware sample rates tend to be even more accurate as noted,
	// for example, in the paper http://www.portaudio.com/docs/portaudio_sync_acmc2003.pdf.
	// Although, software resampling can introduce more significant inaccuracies,
	// e.g. WinMME on my WinXP system works at about 32100Hz instead, while WASAPI, OSS, PulseAudio and ALSA perform much better.
	// Setting 1% as the maximum relative error provides for superior rendering accuracy, and sample rate deviations should now be inaudible.
	// In case there are nasty environments with greater deviations in sample rate, we should make this configurable.
	MasterClockNanos nanosToRender = (newFirstSampleMasterClockNanos + nominalNanosToRender) - firstSampleMasterClockNanos;
	double relativeError = (double)nanosToRender / nominalNanosToRender;
	if (relativeError < 0.99) {
		nanosToRender = 0.99 * nominalNanosToRender;
	}
	if (relativeError > 1.01) {
		nanosToRender = 1.01 * nominalNanosToRender;
	}
	lastSampleMasterClockNanos = firstSampleMasterClockNanos + nanosToRender;
	// Compute actual sample rate so that the actual rendering time interval ends exactly in lastSampleMasterClockNanos point
	return MasterClock::NANOS_PER_SECOND * (double)frameCount / (double)nanosToRender;
}
Example #2
0
MasterClockNanos ClockSync::sync(MasterClockNanos externalNow) {
	MasterClockNanos masterNow = MasterClock::getClockNanos();
	if (externalNow == 0) {
		// Special value meaning "no timestamp, play immediately"
		return masterNow;
	}
	if (!offsetValid) {
		masterStart = masterNow;
		externalStart = externalNow;
		offset = 0;
		offsetShift = 0;
		qDebug() << "ClockSync: init:" << externalNow << masterNow << offset << drift;
		offsetValid = true;
		return masterNow;
	}
	MasterClockNanos masterElapsed = masterNow - masterStart;
	MasterClockNanos externalElapsed = externalNow - externalStart;
	MasterClockNanos offsetNow = masterElapsed - drift * externalElapsed;
	if (masterElapsed > periodicResetNanos) {
		masterStart = masterNow;
		externalStart = externalNow;
		offset -= offsetNow;
		offsetShift = 0;	// we don't want here to shift
		// we rather add a compensation for the offset we have now to the new drift value
		drift = (masterElapsed - offset * periodicDampFactor) / externalElapsed;
		qDebug() << "ClockSync: offset:" << 1e-6 * offset << "drift:" << drift;
		return masterNow + offset;
	}
	if(qAbs(offsetNow - offset) > emergencyResetThresholdNanos) {
		qDebug() << "ClockSync: emergency reset:" << externalNow << masterNow << offset << offsetNow;
		masterStart = masterNow;
		externalStart = externalNow;
		offset = 0;
		offsetShift = 0;
		drift = 1.0;
		return masterNow;
	}
	if (((offsetNow - offset) < lowJitterThresholdNanos) ||
		((offsetNow - offset) > highJitterThresholdNanos)) {
		qDebug() << "ClockSync: Latency resync offset diff:" << 1e-6 * (offsetNow - offset) << "drift:" << drift;
		// start moving offset towards 0 by steps of shiftFactor * offset
		offsetShift = MasterClockNanos(shiftFactor * (offset - offsetNow));
	}
	if (qAbs(offsetShift) > qAbs(offset - offsetNow)) {
		// resync's done
		masterStart = masterNow;
		externalStart = externalNow;
		offset = 0;
		offsetShift = 0;
		drift = 1.0;
		return masterNow;
	}
	offset -= offsetShift;
	if (offsetShift != 0) {
		qDebug() << "ClockSync: offset:" << 1e-6 * offset << "shift:" << 1e-6 * offsetShift;
	}
	return masterStart + offset + drift * externalElapsed;
}
Example #3
0
MasterClockNanos ClockSync::sync(MasterClockNanos masterNow, MasterClockNanos externalNow) {
	if (performResetOnNextSync) {
		masterStart = masterNow;
		externalStart = externalNow;
		baseOffset = externalNow - masterNow;
		offsetSum = 0;
		syncCount = 0;
		drift = 0;
		qDebug() << "ClockSync: reset" << externalNow * 1e-6 << masterNow * 1e-6 << baseOffset * 1e-6 << getDrift();
		performResetOnNextSync = false;
		return masterNow;
	}
	MasterClockNanos masterElapsed = masterNow - masterStart;
	MasterClockNanos externalElapsed = externalNow - externalStart;

	// Use deltas in average offset calculation to avoid overflow
	MasterClockNanos deltaOffset = (externalNow - masterNow) - baseOffset;
	offsetSum += deltaOffset;
	++syncCount;
	MasterClockNanos currentOffset = baseOffset + MasterClockNanos(externalElapsed * drift);
	if (emergencyResetThresholdNanos < qAbs(deltaOffset)) {
		scheduleReset();
	}
	if (periodicResetNanos < masterElapsed) {
		MasterClockNanos newBaseOffset = baseOffset + offsetSum / syncCount;
		drift = (newBaseOffset - currentOffset) / (double)periodicResetNanos;
		masterStart = masterNow;
		externalStart = externalNow;
		baseOffset = currentOffset;
		offsetSum = 0;
		syncCount = 0;
#if 0
		qDebug() << "ClockSync: offset delta" << (newBaseOffset - baseOffset) * 1e-6 << "drift" << getDrift();
#endif
	}
	return externalNow - currentOffset;
}
Example #4
0
void AudioStream::updateTimeInfo(const MasterClockNanos measuredNanos, const quint32 framesInAudioBuffer) {
#if 0
	qDebug() << "R" << renderedFramesCount - timeInfo[timeInfoIx].lastPlayedFramesCount
					<< (measuredNanos - timeInfo[timeInfoIx].lastPlayedNanos) * 1e-6;
#endif
	if ((measuredNanos - timeInfo[timeInfoIx].lastPlayedNanos) < MINIMUM_TIMEINFO_UPDATE_NANOS) {
		// If callbacks are coming too quickly, we cannot benefit from that, it just makes our timing estimation worse...
		// Moreover, we should be able to adjust lastPlayedFramesCount increasing speed as it counts in samples
		return;
	}
	uint nextTimeInfoIx = 1 - timeInfoIx;
	if (clockSync != NULL) {
		MasterClockNanos renderedNanos = MasterClockNanos(renderedFramesCount / (double)sampleRate * MasterClock::NANOS_PER_SECOND);
		timeInfo[nextTimeInfoIx].lastPlayedNanos = clockSync->sync(measuredNanos, renderedNanos);
		timeInfo[nextTimeInfoIx].lastPlayedFramesCount = renderedFramesCount;
		timeInfo[nextTimeInfoIx].actualSampleRate = sampleRate * clockSync->getDrift();
	} else {

		// Number of played frames (assuming no x-runs happend)
		quint64 estimatedNewPlayedFramesCount = quint64(renderedFramesCount - framesInAudioBuffer);
		double secondsElapsed = double(measuredNanos - timeInfo[timeInfoIx].lastPlayedNanos) / MasterClock::NANOS_PER_SECOND;

		// Ensure lastPlayedFramesCount is monotonically increasing and has no jumps
		quint64 newPlayedFramesCount = timeInfo[timeInfoIx].lastPlayedFramesCount + quint64(timeInfo[timeInfoIx].actualSampleRate * secondsElapsed + 0.5);

		// If the estimation goes too far - do reset
		if (qAbs(qint64(estimatedNewPlayedFramesCount - newPlayedFramesCount)) > (qint64)audioLatencyFrames) {
			qDebug() << "AudioStream: Estimated play position is way off:" << qint64(estimatedNewPlayedFramesCount - newPlayedFramesCount) << "-> resetting...";
			timeInfo[nextTimeInfoIx].lastPlayedNanos = measuredNanos;
			timeInfo[nextTimeInfoIx].lastPlayedFramesCount = estimatedNewPlayedFramesCount;
			timeInfo[nextTimeInfoIx].actualSampleRate = sampleRate;
			timeInfoIx = nextTimeInfoIx;
			return;
		}

		double estimatedNewActualSampleRate = ((double)estimatedNewPlayedFramesCount - timeInfo[timeInfoIx].lastPlayedFramesCount) / secondsElapsed;

		// Now fixup sample rate estimation. It shouldn't go too far from expected.
		// Assume the actual sample rate differs from nominal one within 1% range.
		// Actual hardware sample rates tend to be even more accurate as noted,
		// for example, in the paper http://www.portaudio.com/docs/portaudio_sync_acmc2003.pdf.
		// Although, software resampling can introduce more significant inaccuracies,
		// e.g. WinMME on my WinXP system works at about 32100Hz instead, while WASAPI, OSS, PulseAudio and ALSA perform much better.
		// Setting 1% as the maximum permitted relative error provides for superior rendering accuracy, and sample rate deviations should now be inaudible.
		// In case there are nasty environments with greater deviations in sample rate, we should make this configurable.
		double nominalSampleRate = sampleRate;
		double relativeError = estimatedNewActualSampleRate / nominalSampleRate;
		if (relativeError < 0.995) {
			estimatedNewActualSampleRate = 0.995 * nominalSampleRate;
		} else if (relativeError > 1.005) {
			estimatedNewActualSampleRate = 1.005 * nominalSampleRate;
		}
		timeInfo[nextTimeInfoIx].lastPlayedNanos = measuredNanos;
		timeInfo[nextTimeInfoIx].lastPlayedFramesCount = newPlayedFramesCount;
		timeInfo[nextTimeInfoIx].actualSampleRate = estimatedNewActualSampleRate;
#if 0
		qDebug() << "S" << estimatedNewActualSampleRate << int(newPlayedFramesCount - estimatedNewPlayedFramesCount);
#endif
	}
	timeInfoIx = nextTimeInfoIx;
}