diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 6f86ee649..6981b1ed8 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1466,7 +1466,17 @@ bool AudioIO::StartPortAudioStream(double sampleRate, #ifdef EXPERIMENTAL_MIDI_OUT mNumFrames = 0; mNumPauseFrames = 0; + // we want this initial value to be way high. It should be + // sufficient to assume AudioTime is zero and therefore + // mSystemMinusAudioTime is SystemTime(), but we'll add 1000s + // for good measure. On the first callback, this should be + // reduced to SystemTime() - mT0, and note that mT0 is always + // positive. + mSystemMinusAudioTimePlusLatency = + mSystemMinusAudioTime = SystemTime(mUsingAlsa) + 1000; mAudioOutLatency = 0.0; // set when stream is opened + mCallbackCount = 0; + mAudioFramesPerBuffer = 0; #endif mOwningProject = GetActiveProject(); mInputMeter = NULL; @@ -1616,6 +1626,7 @@ bool AudioIO::StartPortAudioStream(double sampleRate, // this is an initial guess, but for PA/Linux/ALSA it's wrong and will be // updated with a better value: mAudioOutLatency = info->outputLatency; + mSystemMinusAudioTimePlusLatency += mAudioOutLatency; } return (mLastPaError == paNoError); @@ -1639,6 +1650,7 @@ void AudioIO::StartMonitoring(double sampleRate) // FIXME: TRAP_ERR StartPortAudioStream (a PaError may be present) // but StartPortAudioStream function only returns true or false. + mUsingAlsa = false; success = StartPortAudioStream(sampleRate, (unsigned int)playbackChannels, (unsigned int)captureChannels, captureFormat); @@ -4146,33 +4158,31 @@ double AudioIO::PauseTime() } +// MidiTime() is an estimate in milliseconds of the current audio +// output (DAC) time + 1s. In other words, what audacity track time +// corresponds to the audio (including pause insertions) at the output? +// PmTimestamp AudioIO::MidiTime() { - //printf("AudioIO:MidiTime: PaUtil_GetTime() %g mAudioCallbackOutputDacTime %g time - outputTime %g\n", - // PaUtil_GetTime(), mAudioCallbackOutputDacTime, PaUtil_GetTime() - mAudioCallbackOutputDacTime); // note: the extra 0.0005 is for rounding. Round down by casting to // unsigned long, then convert to PmTimeStamp (currently signed) - // See long comments at the top of the file for the explanation of this - // calculation; we must use PaUtil_GetTime() here and also in the audio - // callback, to change the origin of times from portaudio, so the diffence of - // now and then is small, as the long comment assumes. - // PRL: the time correction is really Midi latency achieved by different - // means than specifiying it to Pm_OpenStream. The use of the accumulated + // means than specifying it to Pm_OpenStream. The use of the accumulated // sample count generated by the audio callback (in AudioTime()) might also - // have the virtue of keeping the Midi output synched with audio, even though - // pmlinuxalsa.c does not implement any synchronization of its own. + // have the virtue of keeping the Midi output synched with audio. - auto offset = mAudioCallbackOutputDacTime - mAudioCallbackOutputCurrentTime; - - auto clockChange = PaUtil_GetTime() - mAudioCallbackClockTime; - // auto offset = mAudioCallbackOutputDacTime - mAudioCallbackOutputCurrentTime; - return (PmTimestamp) ((unsigned long) (1000 * ( - AudioTime() + 1.0005 - - mAudioFramesPerBuffer / mRate + - clockChange - offset - ))) + MIDI_MINIMAL_LATENCY_MS; + PmTimestamp ts; + // subtract latency here because mSystemMinusAudioTime gets us + // to the current *write* time, but we're writing ahead by audio output + // latency (mAudioOutLatency). + double now = SystemTime(mUsingAlsa); + ts = (PmTimestamp) ((unsigned long) + (1000 * (now + 1.0005 - + mSystemMinusAudioTimePlusLatency))); + // printf("AudioIO::MidiTime() %d time %g sys-aud %g\n", + // ts, now, mSystemMinusAudioTime); + return ts + MIDI_MINIMAL_LATENCY_MS; } @@ -4439,17 +4449,78 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, (float *)alloca(framesPerBuffer*numPlaybackChannels * sizeof(float)) : (float *)outputBuffer; + if (gAudioIO->mCallbackCount++ == 0) { + // This is effectively mSystemMinusAudioTime when the buffer is empty: + gAudioIO->mStartTime = SystemTime(gAudioIO->mUsingAlsa) - gAudioIO->mT0; + // later, mStartTime - mSystemMinusAudioTime will tell us latency + } + #ifdef EXPERIMENTAL_MIDI_OUT /* GSW: Save timeInfo in case MidiPlayback needs it */ gAudioIO->mAudioCallbackClockTime = PaUtil_GetTime(); - gAudioIO->mAudioCallbackOutputDacTime = timeInfo->outputBufferDacTime; - gAudioIO->mAudioCallbackOutputCurrentTime = timeInfo->currentTime; + /* for Linux, estimate a smooth audio time as a slowly-changing + offset from system time */ + // rnow is system time as a double to simplify math + double rnow = SystemTime(gAudioIO->mUsingAlsa); + // anow is next-sample-to-be-computed audio time as a double + double anow = gAudioIO->AudioTime(); + + if (gAudioIO->mUsingAlsa) { + // timeInfo's fields are not all reliable. + + // enow is audio time estimated from our clock synchronization protocol, + // which produces mSystemMinusAudioTime. But we want the estimate + // to drift low, so we steadily increase mSystemMinusAudioTime to + // simulate a fast system clock or a slow audio clock. If anow > enow, + // we'll update mSystemMinusAudioTime to keep in sync. (You might think + // we could just use anow as the "truth", but it has a lot of jitter, + // so we are using enow to smooth out this jitter, in fact to < 1ms.) + // Add worst-case clock drift using previous framesPerBuffer: + const auto increase = + gAudioIO->mAudioFramesPerBuffer * 0.0002 / gAudioIO->mRate; + gAudioIO->mSystemMinusAudioTime += increase; + gAudioIO->mSystemMinusAudioTimePlusLatency += increase; + double enow = rnow - gAudioIO->mSystemMinusAudioTime; + + + // now, use anow instead if it is ahead of enow + if (anow > enow) { + gAudioIO->mSystemMinusAudioTime = rnow - anow; + // Update our mAudioOutLatency estimate during the first 20 callbacks. + // During this period, the buffer should fill. Once we have a good + // estimate of mSystemMinusAudioTime (expected in fewer than 20 callbacks) + // we want to stop the updating in case there is clock drift, which would + // cause the mAudioOutLatency estimation to drift as well. The clock drift + // in the first 20 callbacks should be negligible, however. + if (gAudioIO->mCallbackCount < 20) { + gAudioIO->mAudioOutLatency = gAudioIO->mStartTime - + gAudioIO->mSystemMinusAudioTime; + } + gAudioIO->mSystemMinusAudioTimePlusLatency = + gAudioIO->mSystemMinusAudioTime + gAudioIO->mAudioOutLatency; + } + } + else { + // If not using Alsa, rely on timeInfo to have meaningful values that are + // more precise than the output latency value reported at stream start. + gAudioIO->mSystemMinusAudioTime = rnow - anow; + gAudioIO->mSystemMinusAudioTimePlusLatency = + gAudioIO->mSystemMinusAudioTime + + (timeInfo->outputBufferDacTime - timeInfo->currentTime); + } - // printf("in callback, mAudioCallbackOutputDacTime %g\n", gAudioIO->mAudioCallbackOutputDacTime); //DBG gAudioIO->mAudioFramesPerBuffer = framesPerBuffer; - if(gAudioIO->IsPaused()) + if (gAudioIO->IsPaused() + // PRL: Why was this added? Was it only because of the mysterious + // initial leading zeroes, now solved by setting mStreamToken early? + || gAudioIO->mStreamToken <= 0 + ) gAudioIO->mNumPauseFrames += framesPerBuffer; + + // PRL: Note that when there is a separate MIDI thread, it is effectively + // blocked until the first visit to this line during a playback, and will + // not read gAudioIO->mSystemMinusAudioTimePlusLatency sooner: gAudioIO->mNumFrames += framesPerBuffer; #ifndef USE_MIDI_THREAD diff --git a/src/AudioIO.h b/src/AudioIO.h index 2e9a024c6..506232d62 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -543,12 +543,6 @@ private: /// PortAudio's clock time volatile double mAudioCallbackClockTime; - /// Rely on these two only if not using the Alsa host api: - /// PortAudio's currentTime -- its origin is unspecified! - volatile double mAudioCallbackOutputCurrentTime; - /// PortAudio's outTime - volatile double mAudioCallbackOutputDacTime; - /// Number of frames output, including pauses volatile long mNumFrames; /// How many frames of zeros were output due to pauses? @@ -565,9 +559,27 @@ private: /// stream closing until last message has been delivered PmTimestamp mMaxMidiTimestamp; + /// Offset from ideal sample computation time to system time, + /// where "ideal" means when we would get the callback if there + /// were no scheduling delays or computation time + double mSystemMinusAudioTime; /// audio output latency reported by PortAudio + /// (initially; for Alsa, we adjust it to the largest "observed" value) double mAudioOutLatency; + // Next two are used to adjust the previous two, if + // PortAudio does not provide the info (using ALSA): + + /// time of first callback + /// used to find "observed" latency + double mStartTime; + /// number of callbacks since stream start + long mCallbackCount; + + /// Make just one variable to communicate from audio to MIDI thread, + /// to avoid problems of atomicity of updates + volatile double mSystemMinusAudioTimePlusLatency; + Alg_seq_ptr mSeq; std::unique_ptr mIterator; /// The next event to play (or null) @@ -782,4 +794,3 @@ private: }; #endif -