1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-02 17:23:18 +02:00

Roger's timing correction for Alsa; I unified with non-Alsa case...

... Write only one variable in audacityAudioCallback, to be read (maybe in
another thread) by AudioIO::MidiTime().

The non-Alsa case behaves essentially as before:  it wasn't broken, so it
isn't fixed, though it is rearranged.
This commit is contained in:
Paul Licameli 2017-09-23 23:24:07 -04:00
parent 2b84262314
commit 51296237da
2 changed files with 112 additions and 30 deletions

View File

@ -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

View File

@ -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<Alg_iterator> mIterator;
/// The next event to play (or null)
@ -782,4 +794,3 @@ private:
};
#endif