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:
parent
2b84262314
commit
51296237da
117
src/AudioIO.cpp
117
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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user