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