bool ZKMORHP_IOThreadSlave::CalculateNextWakeUpTime(const AudioTimeStamp& inCurrentTime, AudioTimeStamp& outNextWakeUpTime, bool inMustResynch, bool& inIOGuardWasLocked) { bool theAnswer = true; // static const Float64 kOverloadThreshold = 0.050; static const Float64 kOverloadThreshold = 0.000; bool isDone = false; AudioTimeStamp theCurrentTime = inCurrentTime; int theNumberIterations = 0; while(!isDone) { ++theNumberIterations; Float64 theIOBufferFrameSize = mDevice->GetIOBufferFrameSize(); // set up the outNextWakeUpTime outNextWakeUpTime = CAAudioTimeStamp::kZero; outNextWakeUpTime.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; // set up the overload time AudioTimeStamp theOverloadTime = CAAudioTimeStamp::kZero; theOverloadTime.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; // calculate the sample time for the next wake up time AudioTimeStamp theSampleTime = mAnchorTime; theSampleTime.mFlags = kAudioTimeStampSampleTimeValid; theSampleTime.mSampleTime += mFrameCounter; theSampleTime.mSampleTime += theIOBufferFrameSize; // translate that to a host time mDevice->TranslateTime(theSampleTime, outNextWakeUpTime); // calculate the overload time Float64 theReservedAmount = std::max(0.0, mIOCycleUsage - kOverloadThreshold); theSampleTime = mAnchorTime; theSampleTime.mFlags = kAudioTimeStampSampleTimeValid; theSampleTime.mSampleTime += mFrameCounter; theSampleTime.mSampleTime += theReservedAmount * theIOBufferFrameSize; // translate that to a host time mDevice->TranslateTime(theSampleTime, theOverloadTime); if(inMustResynch || (theCurrentTime.mHostTime >= theOverloadTime.mHostTime)) { // tell the device what happenned mDevice->GetIOCycleTelemetry().IOCycleWorkLoopOverloadBegin(mIOCycleCounter, theCurrentTime, theOverloadTime); // the current time is beyond the overload time, have to resynchronize #if Log_Resynchs if(inMustResynch) { DebugMessageN1("ZKMORHP_IOThreadSlave::CalculateNextWakeUpTime: resynch was forced %d", theNumberIterations); } else { DebugMessageN1("ZKMORHP_IOThreadSlave::CalculateNextWakeUpTime: wake up time is in the past... resynching %d", theNumberIterations); DebugMessageN3(" Now: %qd Overload: %qd Difference: %qd", CAHostTimeBase::ConvertToNanos(theCurrentTime.mHostTime), CAHostTimeBase::ConvertToNanos(theOverloadTime.mHostTime), CAHostTimeBase::ConvertToNanos(theCurrentTime.mHostTime - theOverloadTime.mHostTime)); } #endif // notify clients that the overload has taken place if(inIOGuardWasLocked) { mIOGuard.Unlock(); } CAPropertyAddress theOverloadAddress(kAudioDeviceProcessorOverload); mDevice->PropertiesChanged(1, &theOverloadAddress); inIOGuardWasLocked = mIOGuard.Lock(); // re-anchor at the current time theCurrentTime.mSampleTime = 0; theCurrentTime.mHostTime = 0; if(mDevice->EstablishIOCycleAnchorTime(theCurrentTime)) { Resynch(&theCurrentTime, false); } else { theAnswer = false; isDone = true; } // reset the forced resynch flag inMustResynch = false; mDevice->GetIOCycleTelemetry().IOCycleWorkLoopOverloadEnd(mIOCycleCounter, mAnchorTime); } else { // still within the limits isDone = true; } } // adjust the counter depending on what happenned if(theNumberIterations > 1) { // we went through the calculation more than once, which means an overload happenned ++mOverloadCounter; } else { // only did the calculation once, so no overload occurred mOverloadCounter = 0; } return theAnswer; }
void ZKMORHP_IOThreadSlave::WorkLoopIteration(bool& isInNeedOfResynch) { if (mStopWorkLoop) { WorkLoopTeardown(); return; } try { bool wasLocked = mIOGuard.Lock(); // bool wasLocked; wasLocked = mIOGuard.Try(wasLocked); // get the current time AudioTimeStamp theCurrentTime; mDevice->GetCurrentTime(theCurrentTime); // increment the counter ++mIOCycleCounter; // do IO if the thread wasn't stopped if(!mStopWorkLoop) { if(theCurrentTime.mSampleTime >= (mAnchorTime.mSampleTime + mFrameCounter)) { // increment the frame counter mFrameCounter += mDevice->GetIOBufferFrameSize(); // the new cycle is starting mDevice->GetIOCycleTelemetry().IOCycleWorkLoopBegin(mIOCycleCounter, theCurrentTime); if(mDevice->UpdateIOCycleTimingServices()) { // something unexpected happened with the time stamp, so resynch prior to doing IO AudioTimeStamp theNewAnchor = CAAudioTimeStamp::kZero; theNewAnchor.mSampleTime = 0; theNewAnchor.mHostTime = 0; theNewAnchor.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; if(mDevice->EstablishIOCycleAnchorTime(theNewAnchor)) { Resynch(&theNewAnchor, false); } else { Resynch(NULL, false); } // re-get the current time too mDevice->GetCurrentTime(theCurrentTime); } // do the IO isInNeedOfResynch = PerformIO(theCurrentTime); } } // calculate the next wake up time AudioTimeStamp theNextWakeUpTime = CAAudioTimeStamp::kZero; theNextWakeUpTime.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; // bool wasLocked = false; // bool wasLocked = mIOGuard.Lock(); if(CalculateNextWakeUpTime(theCurrentTime, theNextWakeUpTime, isInNeedOfResynch, wasLocked)) { mDevice->GetIOCycleTelemetry().IOCycleWorkLoopEnd(mIOCycleCounter, theCurrentTime, theNextWakeUpTime); } // execute any deferred commands mDevice->ExecuteAllCommands(); if (wasLocked) mIOGuard.Unlock(); } catch(const CAException& inException) { DebugMessageN1("ZKMORHP_IOThreadSlave::WorkLoopIteration: Caught a CAException, code == %ld", (long int)inException.GetError()); } catch(...) { DebugMessage("ZKMORHP_IOThreadSlave::WorkLoopIteration: Caught an unknown exception."); } }
void ZKMORHP_IOThreadSlave::WorkLoop() { // grab the IO guard bool wasLocked = mIOGuard.Lock(); // initialize some stuff mWorkLoopPhase = kInitializingPhase; mIOCycleCounter = 0; mOverloadCounter = 0; CAPropertyAddress theIsRunningAddress(kAudioDevicePropertyDeviceIsRunning); mDevice->GetIOCycleTelemetry().IOCycleInitializeBegin(mIOCycleCounter); try { // and signal that the IO thread is running mIOGuard.NotifyAll(); // initialize the work loop stopping conditions mStopWorkLoop = false; // Tell the device that the IO thread has initialized. Note that we unlock around this call // due to the fact that IOCycleInitialize might not return for a while because it might // have to wait for the hardware to start. if(wasLocked) { mIOGuard.Unlock(); } // tell the device that the IO cycle is initializing to start the timing services mDevice->StartIOCycleTimingServices(); // set the device state to know the engine is running mDevice->IOEngineStarted(); // notify clients that the engine is running mDevice->PropertiesChanged(1, &theIsRunningAddress); // re-lock the guard wasLocked = mIOGuard.Lock(); // make sure the thread is still running before moving on if(!mStopWorkLoop) { // set the time constraints for the IOThread SetTimeConstraints(); // initialize the clock mDevice->EstablishIOCycleAnchorTime(mAnchorTime); mFrameCounter = 0; #if Offset_For_Input if(mDevice->HasInputStreams()) { // the first sleep cycle as to be at least the input safety offset and a buffer's // worth of time to be sure that the input data is all there mFrameCounter += mDevice->GetSafetyOffset(true); } #endif // enter the work loop mWorkLoopPhase = kRunningPhase; bool isInNeedOfResynch = false; mDevice->GetIOCycleTelemetry().IOCycleInitializeEnd(mIOCycleCounter, mAnchorTime); while(!mStopWorkLoop) { // get the current time AudioTimeStamp theCurrentTime; mDevice->GetCurrentTime(theCurrentTime); // calculate the next wake up time AudioTimeStamp theNextWakeUpTime = CAAudioTimeStamp::kZero; theNextWakeUpTime.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; if(CalculateNextWakeUpTime(theCurrentTime, theNextWakeUpTime, isInNeedOfResynch, wasLocked)) { // sleep until the next wake up time mDevice->GetIOCycleTelemetry().IOCycleWorkLoopEnd(mIOCycleCounter, theCurrentTime, theNextWakeUpTime); mIOGuard.WaitUntil(CAHostTimeBase::ConvertToNanos(theNextWakeUpTime.mHostTime)); // increment the counter ++mIOCycleCounter; // do IO if the thread wasn't stopped if(!mStopWorkLoop) { // get the current time mDevice->GetCurrentTime(theCurrentTime); #if Log_SchedulingLatency // check to see if we have incurred a large scheduling latency if(theCurrentTime.mHostTime > (theNextWakeUpTime.mHostTime + mAllowedLatency)) { // log it mLatencyLog->Capture(theNextWakeUpTime.mHostTime - mAllowedLatency, theCurrentTime.mHostTime, true); // print how late we are DebugMessageN1("HP_IOThread::WorkLoop: woke up late by %f milliseconds", ((Float64)CAHostTimeBase::ConvertToNanos(theCurrentTime.mHostTime - theNextWakeUpTime.mHostTime)) / (1000.0 * 1000.0)); } #endif if(theCurrentTime.mSampleTime >= (mAnchorTime.mSampleTime + mFrameCounter)) { // increment the frame counter mFrameCounter += mDevice->GetIOBufferFrameSize(); // the new cycle is starting mDevice->GetIOCycleTelemetry().IOCycleWorkLoopBegin(mIOCycleCounter, theCurrentTime); if(mDevice->UpdateIOCycleTimingServices()) { // something unexpected happenned with the time stamp, so resynch prior to doing IO AudioTimeStamp theNewAnchor = CAAudioTimeStamp::kZero; theNewAnchor.mSampleTime = 0; theNewAnchor.mHostTime = 0; theNewAnchor.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; if(mDevice->EstablishIOCycleAnchorTime(theNewAnchor)) { Resynch(&theNewAnchor, false); } else { Resynch(NULL, false); } // re-get the current time too mDevice->GetCurrentTime(theCurrentTime); } // do the IO isInNeedOfResynch = PerformIO(theCurrentTime); } } } else { // calculating the next wake up time failed, so we just stop everything (which // will get picked up when the commands are executed mDevice->ClearAllCommands(); mDevice->Do_StopAllIOProcs(); } // execute any deferred commands mDevice->ExecuteAllCommands(); } } mWorkLoopPhase = kTeardownPhase; mDevice->GetIOCycleTelemetry().IOCycleTeardownBegin(mIOCycleCounter); // the work loop has finished, clear the time constraints ClearTimeConstraints(); // tell the device that the IO thread is torn down mDevice->StopIOCycleTimingServices(); } catch(const CAException& inException) { DebugMessageN1("HP_IOThread::WorkLoop: Caught a CAException, code == %ld", (long int)inException.GetError()); } catch(...) { DebugMessage("HP_IOThread::WorkLoop: Caught an unknown exception."); } // set the device state to know the engine has stopped mDevice->IOEngineStopped(); // Notify clients that the IO thread is stopping. Note that we unlock around this call // due to the fact that clients might want to call back into the HAL. if(wasLocked) { mIOGuard.Unlock(); } // Notify clients that the IO thread is stopping mDevice->PropertiesChanged(1, &theIsRunningAddress); // re-lock the guard wasLocked = mIOGuard.Lock(); mDevice->GetIOCycleTelemetry().IOCycleTeardownEnd(mIOCycleCounter); mWorkLoopPhase = kNotRunningPhase; mIOGuard.NotifyAll(); mIOCycleCounter = 0; if(wasLocked) { mIOGuard.Unlock(); } }
void HP_IOThread::WorkLoop() { // grab the IO guard bool wasLocked = mIOGuard.Lock(); // initialize some stuff mWorkLoopPhase = kInitializingPhase; mIOCycleCounter = 0; mOverloadCounter = 0; CAPropertyAddress theIsRunningAddress(kAudioDevicePropertyDeviceIsRunning); #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleInitializeBegin(mIOCycleCounter); #else HAL_IOCYCLEINITIALIZEBEGIN(mIOCycleCounter); #endif try { // and signal that the IO thread is running mIOGuard.NotifyAll(); // initialize the work loop stopping conditions mStopWorkLoop = false; // Tell the device that the IO thread has initialized. Note that we unlock around this call // due to the fact that IOCycleInitialize might not return for a while because it might // have to wait for the hardware to start. if(wasLocked) { mIOGuard.Unlock(); } // tell the device that the IO cycle is initializing to start the timing services mDevice->StartIOCycleTimingServices(); // set the device state to know the engine is running mDevice->IOEngineStarted(); // notify clients that the engine is running mDevice->PropertiesChanged(1, &theIsRunningAddress); // re-lock the guard wasLocked = mIOGuard.Lock(); // make sure the thread is still running before moving on if(!mStopWorkLoop) { // set the time constraints for the IOThread SetTimeConstraints(); // initialize the clock mDevice->EstablishIOCycleAnchorTime(mAnchorTime); mFrameCounter = 0; #if Offset_For_Input if(mDevice->HasInputStreams()) { // the first sleep cycle as to be at least the input safety offset and a buffer's // worth of time to be sure that the input data is all there mFrameCounter += mDevice->GetSafetyOffset(true); } #endif // get the current time AudioTimeStamp theCurrentTime; mDevice->GetCurrentTime(theCurrentTime); // enter the work loop mWorkLoopPhase = kRunningPhase; bool isInNeedOfResynch = false; bool isCheckingForOverloads = false; Float64 theIOBufferFrameSize = mDevice->GetIOBufferFrameSize(); #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleInitializeEnd(mIOCycleCounter, mAnchorTime); mDevice->GetIOCycleTelemetry().IOCycleWorkLoopBegin(mIOCycleCounter, theCurrentTime); #else HAL_IOCYCLEINITIALIZEEND(mIOCycleCounter, mAnchorTime); HAL_IOCYCLEWORKLOOPBEGIN(mIOCycleCounter, theCurrentTime); #endif while(!mStopWorkLoop) { // get the new IO buffer frame size Float64 theNewIOBufferFrameSize = mDevice->GetIOBufferFrameSize(); // initialize the next wake up time AudioTimeStamp theNextWakeUpTime = CAAudioTimeStamp::kZero; theNextWakeUpTime.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; // get the current time mDevice->GetCurrentTime(theCurrentTime); // we have to run a special, untimed IO cycle if the IO buffer size changed if((theNewIOBufferFrameSize != theIOBufferFrameSize) && (theCurrentTime.mSampleTime >= (mAnchorTime.mSampleTime + mFrameCounter))) { // mark the end of the previous cycle #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleWorkLoopEnd(mIOCycleCounter, theCurrentTime, theNextWakeUpTime); #else HAL_IOCYCLEWORKLOOPEND(mIOCycleCounter, theCurrentTime, theNextWakeUpTime); #endif // increment the cycle counter ++mIOCycleCounter; // increment the frame counter mFrameCounter += theIOBufferFrameSize; // the new cycle is starting #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleWorkLoopBegin(mIOCycleCounter, theCurrentTime); #else HAL_IOCYCLEWORKLOOPBEGIN(mIOCycleCounter, theCurrentTime); #endif // do the IO, note that we don't need to update the timing services for this special cycle isInNeedOfResynch = PerformIO(theCurrentTime, theIOBufferFrameSize); // turn off overload checking for the next cycle to be nice to clients isCheckingForOverloads = false; } // calculate the next wake up time if(CalculateNextWakeUpTime(theCurrentTime, theIOBufferFrameSize, theNextWakeUpTime, isCheckingForOverloads, isInNeedOfResynch, wasLocked)) { // sleep until the next wake up time #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleWorkLoopEnd(mIOCycleCounter, theCurrentTime, theNextWakeUpTime); #else HAL_IOCYCLEWORKLOOPEND(mIOCycleCounter, theCurrentTime, theNextWakeUpTime); #endif mIOGuard.WaitUntil(CAHostTimeBase::ConvertToNanos(theNextWakeUpTime.mHostTime)); // increment the cycle counter ++mIOCycleCounter; // make sure overload checking is enabled isCheckingForOverloads = true; // do IO if the thread wasn't stopped if(!mStopWorkLoop) { // get the current time mDevice->GetCurrentTime(theCurrentTime); if(theCurrentTime.mSampleTime >= (mAnchorTime.mSampleTime + mFrameCounter)) { // increment the frame counter mFrameCounter += theIOBufferFrameSize; // refresh the current buffer size theIOBufferFrameSize = theNewIOBufferFrameSize; // the new cycle is starting #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleWorkLoopBegin(mIOCycleCounter, theCurrentTime); #else HAL_IOCYCLEWORKLOOPBEGIN(mIOCycleCounter, theCurrentTime); #endif if(mDevice->UpdateIOCycleTimingServices()) { // something unexpected happenned with the time stamp, so resynch prior to doing IO AudioTimeStamp theNewAnchor = CAAudioTimeStamp::kZero; theNewAnchor.mSampleTime = 0; theNewAnchor.mHostTime = 0; theNewAnchor.mFlags = kAudioTimeStampSampleTimeValid + kAudioTimeStampHostTimeValid + kAudioTimeStampRateScalarValid; if(mDevice->EstablishIOCycleAnchorTime(theNewAnchor)) { Resynch(&theNewAnchor, false); } else { Resynch(NULL, false); } // re-get the current time too mDevice->GetCurrentTime(theCurrentTime); // mark the telemetry #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().Resynch(GetIOCycleNumber(), mAnchorTime); #else HAL_RESYNCH(GetIOCycleNumber(), mAnchorTime); #endif } // do the IO isInNeedOfResynch = PerformIO(theCurrentTime, theIOBufferFrameSize); } } } else { // calculating the next wake up time failed, so we just stop everything (which // will get picked up when the commands are executed mDevice->ClearAllCommands(); mDevice->Do_StopAllIOProcs(); } // execute any deferred commands mDevice->ExecuteAllCommands(); } } mWorkLoopPhase = kTeardownPhase; #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleTeardownBegin(mIOCycleCounter); #else HAL_IOCYCLETEARDOWNBEGIN(mIOCycleCounter); #endif // the work loop has finished, clear the time constraints ClearTimeConstraints(); // tell the device that the IO thread is torn down mDevice->StopIOCycleTimingServices(); } catch(const CAException& inException) { DebugMessageN1("HP_IOThread::WorkLoop: Caught a CAException, code == %ld", (long int)inException.GetError()); } catch(...) { DebugMessage("HP_IOThread::WorkLoop: Caught an unknown exception."); } // set the device state to know the engine has stopped mDevice->IOEngineStopped(); // Notify clients that the IO thread is stopping. Note that we unlock around this call // due to the fact that clients might want to call back into the HAL. if(wasLocked) { mIOGuard.Unlock(); } // Notify clients that the IO thread is stopping mDevice->PropertiesChanged(1, &theIsRunningAddress); // re-lock the guard wasLocked = mIOGuard.Lock(); #if Use_HAL_Telemetry mDevice->GetIOCycleTelemetry().IOCycleTeardownEnd(mIOCycleCounter); #else HAL_IOCYCLETEARDOWNEND(mIOCycleCounter); #endif mWorkLoopPhase = kNotRunningPhase; mIOGuard.NotifyAll(); mIOCycleCounter = 0; if(wasLocked) { mIOGuard.Unlock(); } }