New jack_get_cycle_times() implementation from Fons Adriennsen.

This commit is contained in:
Stephane Letz 2012-03-21 10:21:21 +01:00
parent 94cb2ee9a1
commit 6483c0d5eb
6 changed files with 230 additions and 21 deletions

View File

@ -180,10 +180,15 @@ extern "C"
LIB_EXPORT int jack_engine_takeover_timebase(jack_client_t *);
LIB_EXPORT jack_nframes_t jack_frames_since_cycle_start(const jack_client_t *);
LIB_EXPORT jack_time_t jack_get_time();
LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t time);
LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t usecs);
LIB_EXPORT jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t frames);
LIB_EXPORT jack_nframes_t jack_frame_time(const jack_client_t *);
LIB_EXPORT jack_nframes_t jack_last_frame_time(const jack_client_t *client);
LIB_EXPORT int jack_get_cycle_times(const jack_client_t *client,
jack_nframes_t *current_frames,
jack_time_t *current_usecs,
jack_time_t *next_usecs,
float *period_usecs);
LIB_EXPORT float jack_cpu_load(jack_client_t *client);
LIB_EXPORT jack_native_thread_t jack_client_thread_id(jack_client_t *);
LIB_EXPORT void jack_set_error_function(print_function);
@ -1331,7 +1336,7 @@ LIB_EXPORT jack_time_t jack_frames_to_time(const jack_client_t* ext_client, jack
}
}
LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t* ext_client, jack_time_t time)
LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t* ext_client, jack_time_t usecs)
{
JackGlobals::CheckContext("jack_time_to_frames");
@ -1344,7 +1349,7 @@ LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t* ext_client, j
JackEngineControl* control = GetEngineControl();
if (control) {
control->ReadFrameTime(&timer);
return timer.Time2Frames(time, control->fBufferSize);
return timer.Time2Frames(usecs, control->fBufferSize);
} else {
return 0;
}
@ -1366,6 +1371,24 @@ LIB_EXPORT jack_nframes_t jack_last_frame_time(const jack_client_t* ext_client)
return (control) ? control->fFrameTimer.ReadCurrentState()->CurFrame() : 0;
}
LIB_EXPORT int jack_get_cycle_times(const jack_client_t *client,
jack_nframes_t *current_frames,
jack_time_t *current_usecs,
jack_time_t *next_usecs,
float *period_usecs)
{
JackGlobals::CheckContext("jack_get_cycle_times");
JackEngineControl* control = GetEngineControl();
if (control) {
JackTimer timer;
control->ReadFrameTime(&timer);
return timer.GetCycleTimes(current_frames, current_usecs, next_usecs, period_usecs);
} else {
return 1;
}
}
LIB_EXPORT float jack_cpu_load(jack_client_t* ext_client)
{
JackGlobals::CheckContext("jack_cpu_load");

View File

@ -97,10 +97,11 @@ void JackEngineControl::ResetRollingUsecs()
void JackEngineControl::NotifyXRun(jack_time_t callback_usecs, float delayed_usecs)
{
ResetFrameTime(callback_usecs);
ResetFrameTime(callback_usecs); // Is this still necessary? SL 03/21/2012
fXrunDelayedUsecs = delayed_usecs;
if (delayed_usecs > fMaxDelayedUsecs)
if (delayed_usecs > fMaxDelayedUsecs) {
fMaxDelayedUsecs = delayed_usecs;
}
}
} // end of namespace

View File

@ -43,14 +43,26 @@ JackTimer::JackTimer()
fCurrentWakeup = 0;
fCurrentCallback = 0;
fNextWakeUp = 0;
fFilterCoefficient = 0.01f;
fSecondOrderIntegrator = 0.0f;
fPeriodUsecs = 0.0f;
fFilterOmega = 0.0f; /* Initialised later */
}
jack_nframes_t JackTimer::Time2Frames(jack_time_t time, jack_nframes_t buffer_size)
jack_nframes_t JackTimer::Time2Frames(jack_time_t usecs, jack_nframes_t buffer_size)
{
if (fInitialized) {
return fFrames + (long)rint(((double) ((long long)(time - fCurrentWakeup)) / ((long long)(fNextWakeUp - fCurrentWakeup))) * buffer_size);
/*
Make sure we have signed differences. It would make a lot of sense
to use the standard signed intNN_t types everywhere instead of e.g.
jack_nframes_t and jack_time_t. This would at least ensure that the
types used below are the correct ones. There is no way to get a type
that would be 'a signed version of jack_time_t' for example - the
types below are inherently fragile and there is no automatic way to
check they are the correct ones. The only way is to check manually
against jack/types.h. FA - 16/02/2012
*/
int64_t du = usecs - fCurrentWakeup;
int64_t dp = fNextWakeUp - fCurrentWakeup;
return fFrames + (int32_t)rint((double)du / (double)dp * buffer_size);
} else {
return 0;
}
@ -59,12 +71,37 @@ jack_nframes_t JackTimer::Time2Frames(jack_time_t time, jack_nframes_t buffer_si
jack_time_t JackTimer::Frames2Time(jack_nframes_t frames, jack_nframes_t buffer_size)
{
if (fInitialized) {
return fCurrentWakeup + (long)rint(((double) ((long long)(frames - fFrames)) * ((long long)(fNextWakeUp - fCurrentWakeup))) / buffer_size);
/*
Make sure we have signed differences. It would make a lot of sense
to use the standard signed intNN_t types everywhere instead of e.g.
jack_nframes_t and jack_time_t. This would at least ensure that the
types used below are the correct ones. There is no way to get a type
that would be 'a signed version of jack_time_t' for example - the
types below are inherently fragile and there is no automatic way to
check they are the correct ones. The only way is to check manually
against jack/types.h. FA - 16/02/2012
*/
int32_t df = frames - fFrames;
int64_t dp = fNextWakeUp - fCurrentWakeup;
return fCurrentWakeup + (int64_t)rint((double) df * (double) dp / buffer_size);
} else {
return 0;
}
}
int JackTimer::GetCycleTimes(jack_nframes_t* current_frames, jack_time_t* current_usecs, jack_time_t* next_usecs, float* period_usecs)
{
if (fInitialized) {
*current_frames = fFrames;
*current_usecs = fCurrentWakeup;
*next_usecs = fNextWakeUp;
*period_usecs = fPeriodUsecs;
return 0;
} else {
return 1;
}
}
jack_nframes_t JackTimer::FramesSinceCycleStart(jack_time_t cur_time, jack_nframes_t frames_rate)
{
return (jack_nframes_t) floor((((float)frames_rate) / 1000000.0f) * (cur_time - fCurrentCallback));
@ -80,9 +117,9 @@ void JackFrameTimer::IncFrameTime(jack_nframes_t buffer_size, jack_time_t callba
if (fFirstWakeUp) {
InitFrameTimeAux(callback_usecs, period_usecs);
fFirstWakeUp = false;
} else {
IncFrameTimeAux(buffer_size, callback_usecs, period_usecs);
}
IncFrameTimeAux(buffer_size, callback_usecs, period_usecs);
}
void JackFrameTimer::ResetFrameTime(jack_nframes_t frames_rate, jack_time_t callback_usecs, jack_time_t period_usecs)
@ -119,10 +156,44 @@ void JackFrameTimer::ReadFrameTime(JackTimer* timer)
void JackFrameTimer::InitFrameTimeAux(jack_time_t callback_usecs, jack_time_t period_usecs)
{
/* the first wakeup or post-freewheeling or post-xrun */
/* There seems to be no significant difference between
the two conditions OR-ed above. Incrementing the
frame_time after an xrun shouldn't harm, as there
will be a discontinuity anyway. So the two are
combined in this version.
FA 16/03/2012
*/
/* Since the DLL *will* be run, next_wakeup should be the
current wakeup time *without* adding the period time, as
if it were computed in the previous period.
FA 16/03/2012
*/
/* Added initialisation of timer->period_usecs, required
due to the modified implementation of the DLL itself.
OTOH, this should maybe not be repeated after e.g.
freewheeling or an xrun, as the current value would be
more accurate than the nominal one. But it doesn't really
harm either. Implementing this would require a new flag
in the engine structure, to be used after freewheeling
or an xrun instead of first_wakeup. I don't know if this
can be done without breaking compatibility, so I did not
add this
FA 13/02/2012
*/
/* Added initialisation of timer->filter_omega. This makes
the DLL bandwidth independent of the actual period time.
The bandwidth is now 1/8 Hz in all cases. The value of
timer->filter_omega is 2 * pi * BW * Tperiod.
FA 13/02/2012
*/
JackTimer* timer = WriteNextStateStart();
timer->fSecondOrderIntegrator = 0.0f;
timer->fPeriodUsecs = (float)period_usecs;
timer->fCurrentCallback = callback_usecs;
timer->fNextWakeUp = callback_usecs + period_usecs;
timer->fNextWakeUp = callback_usecs;
timer->fFilterOmega = period_usecs * 7.854e-7f;
WriteNextStateStop();
TrySwitchState(); // always succeed since there is only one writer
}
@ -130,13 +201,38 @@ void JackFrameTimer::InitFrameTimeAux(jack_time_t callback_usecs, jack_time_t pe
void JackFrameTimer::IncFrameTimeAux(jack_nframes_t buffer_size, jack_time_t callback_usecs, jack_time_t period_usecs)
{
JackTimer* timer = WriteNextStateStart();
float delta = (int64_t)callback_usecs - (int64_t)timer->fNextWakeUp;
/* Modified implementation (the actual result is the same).
'fSecondOrderIntegrator' is renamed to 'fPeriodUsecs'
and now represents the DLL's best estimate of the
period time in microseconds (before it was a scaled
version of the difference w.r.t. the nominal value).
This allows this value to be made available to clients
that are interested in it (see jack_get_cycle_times).
This change also means that 'fPeriodUsecs' must be
initialised to the nominal period time instead of zero.
This is done in the first cycle in jack_run_cycle().
'fFilterCoefficient' is renamed to 'fFilterOmega'. It
is now equal to the 'omega' value as defined in the
'Using a DLL to filter time' paper (before it was a
scaled version of this value). It is computed once in
jack_run_cycle() rather than set to a fixed value. This
makes the DLL bandwidth independent of the period time.
FA 13/02/2012
*/
float delta = (float)((int64_t)callback_usecs - (int64_t)timer->fNextWakeUp);
delta *= timer->fFilterOmega;
timer->fCurrentWakeup = timer->fNextWakeUp;
timer->fCurrentCallback = callback_usecs;
timer->fFrames += buffer_size;
timer->fSecondOrderIntegrator += 0.5f * timer->fFilterCoefficient * delta;
timer->fNextWakeUp = timer->fCurrentWakeup + period_usecs + (int64_t) floorf((timer->fFilterCoefficient * (delta + timer->fSecondOrderIntegrator)));
timer->fPeriodUsecs += timer->fFilterOmega * delta;
timer->fNextWakeUp += (int64_t)floorf(timer->fPeriodUsecs + 1.41f * delta + 0.5f);
timer->fInitialized = true;
WriteNextStateStop();
TrySwitchState(); // always succeed since there is only one writer
}

View File

@ -44,8 +44,8 @@ class SERVER_EXPORT JackTimer
jack_time_t fCurrentWakeup;
jack_time_t fCurrentCallback;
jack_time_t fNextWakeUp;
float fSecondOrderIntegrator;
float fFilterCoefficient; /* set once, never altered */
float fPeriodUsecs;
float fFilterOmega; /* set once, never altered */
bool fInitialized;
public:
@ -57,6 +57,7 @@ class SERVER_EXPORT JackTimer
jack_nframes_t Time2Frames(jack_time_t time, jack_nframes_t buffer_size);
jack_time_t Frames2Time(jack_nframes_t frames, jack_nframes_t buffer_size);
jack_nframes_t FramesSinceCycleStart(jack_time_t cur_time, jack_nframes_t frames_rate);
int GetCycleTimes(jack_nframes_t* current_frames, jack_time_t* current_usecs, jack_time_t* next_usecs, float* period_usecs);
jack_nframes_t CurFrame()
{

View File

@ -1284,6 +1284,55 @@ jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT
*/
jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT;
/**
* This function may only be used from the process callback.
* It provides the internal cycle timing information as used by
* most of the other time related functions. This allows the
* caller to map between frame counts and microseconds with full
* precision (i.e. without rounding frame times to integers),
* and also provides e.g. the microseconds time of the start of
* the current cycle directly (it has to be computed otherwise).
*
* If the return value is zero, the following information is
* provided in the variables pointed to by the arguments:
*
* current_frames: the frame time counter at the start of the
* current cycle, same as jack_last_frame_time().
* current_usecs: the microseconds time at the start of the
* current cycle.
* next_usecs: the microseconds time of the start of the next
* next cycle as computed by the DLL.
* period_usecs: the current best estimate of the period time in
* microseconds.
*
* NOTES:
*
* Because of the types used, all the returned values except period_usecs
* are unsigned. In computations mapping between frames and microseconds
* *signed* differences are required. The easiest way is to compute those
* separately and assign them to the appropriate signed variables,
* int32_t for frames and int64_t for usecs. See the implementation of
* jack_frames_to_time() and Jack_time_to_frames() for an example.
*
* Unless there was an xrun, skipped cycles, or the current cycle is the
* first after freewheeling or starting Jack, the value of current_usecs
* will always be the value of next_usecs of the previous cycle.
*
* The value of period_usecs will in general NOT be exactly equal to
* the difference of next_usecs and current_usecs. This is because to
* ensure stability of the DLL and continuity of the mapping, a fraction
* of the loop error must be included in next_usecs. For an accurate
* mapping between frames and microseconds, the difference of next_usecs
* and current_usecs should be used, and not period_usecs.
*
* @return zero if OK, non-zero otherwise.
*/
int jack_get_cycle_times(const jack_client_t *client,
jack_nframes_t *current_frames,
jack_time_t *current_usecs,
jack_time_t *next_usecs,
float *period_usecs) JACK_OPTIONAL_WEAK_EXPORT;
/**
* @return the estimated time in microseconds of the specified frame time
*/

View File

@ -444,7 +444,7 @@ static void* jack_thread(void *arg)
jack_nframes_t last_thread_time = jack_frame_time(client);
while (1) {
jack_nframes_t frames = jack_cycle_wait (client);
jack_nframes_t frames = jack_cycle_wait(client);
jack_nframes_t current_thread_time = jack_frame_time(client);
jack_nframes_t delta_time = current_thread_time - last_thread_time;
Log("jack_thread : delta_time = %ld\n", delta_time);
@ -489,6 +489,36 @@ int process4(jack_nframes_t nframes, void *arg)
return 0;
}
int process5(jack_nframes_t nframes, void *arg)
{
jack_client_t* client = (jack_client_t*) arg;
static jack_nframes_t first_current_frames;
static jack_time_t first_current_usecs;
static jack_time_t first_next_usecs;
static float first_period_usecs;
static int res1 = jack_get_cycle_times(client, &first_current_frames, &first_current_usecs, &first_next_usecs, &first_period_usecs);
jack_nframes_t current_frames;
jack_time_t current_usecs;
jack_time_t next_usecs;
float period_usecs;
int res = jack_get_cycle_times(client, &current_frames, &current_usecs, &next_usecs, &period_usecs);
if (res != 0) {
printf("!!! ERROR !!! jack_get_cycle_times fails...\n");
return 0;
}
Log("calling process5 callback : jack_get_cycle_times delta current_frames = %ld delta current_usecs = %ld delta next_usecs = %ld period_usecs = %f\n",
current_frames - first_current_frames, current_usecs - first_current_usecs, next_usecs - first_next_usecs, period_usecs);
first_current_frames = current_frames;
first_current_usecs = current_usecs;
first_next_usecs = next_usecs;
return 0;
}
static void display_transport_state()
{
jack_transport_state_t ts;
@ -1967,7 +1997,16 @@ int main (int argc, char *argv[])
jack_set_process_callback(client1, process4, client1);
jack_activate(client1);
jack_sleep(2 * 1000);
/**
* Checking jack_get_cycle_times.
*/
Log("Testing jack_get_cycle_times...\n");
jack_deactivate(client1);
jack_set_process_callback(client1, process5, client1);
jack_activate(client1);
jack_sleep(3 * 1000);
/**
* Checking alternate thread model