diff -r 000000000000 -r 5d29cba61097 omxilvideocomps/omxilclock/src/clocksupervisor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/omxilvideocomps/omxilclock/src/clocksupervisor.cpp Fri Oct 08 22:09:17 2010 +0100 @@ -0,0 +1,1668 @@ +/* +* Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: +* +*/ + + +// NOTE: Assumes OMX_SKIP64BIT is not set when building OMX CORE + +#include +#include // OMX port +#include // OMX version number +#include "clocksupervisor.h" +#include "comxilclockprocessingfunction.h" +#include "clockpanics.h" +#include "omxilclock.hrh" + +#include "log.h" + +////////////// +// File scoped constants +// + +/** The maximum number of simultaneous outstanding requests (across all ports) */ +static const TUint KMaxRequests = 20; +static const TUint KThreadStackSize = 1024; + +/** + * Assumption that timer will always take at least this long to complete. + * How soon an outstanding delay must be before we complete immediately instead of setting a timer. + */ +static const TInt KMinTimerOverhead = 1000; // 1000usecs = 1ms + +// assumption that timers go off at some time rounded to this many microseconds +static const TInt KTimerQuantization = +#ifdef __WINSCW__ + 15000; +#else + 1; +#endif + +// aim to select a counter accurate to ~1ms +#ifdef __WINSCW__ +#define USE_FASTCOUNTER // typically faster than 1ms on emulator, 1ms on hardware +#else +#define USE_NTICKCOUNT // typically 5ms on emulator, 1ms on hardware +#endif + +#if !defined(USE_FASTCOUNTER) && !defined(USE_NTICKCOUNT) +#error Either USE_FASTCOUNTER or USE_NTICKCOUNT must be defined +#endif + +/** + * Structure to map OMX_INDEXTYPE to behaviour required by the Clock component. + */ + +// Indexes for time configs start after OMX_IndexTimeStartUnused = 0x09000000 +// Simply deduct this to index this table + +static const OMX_INDEXTYPE KMinJumpTableIndex = OMX_IndexConfigTimeScale; +static const OMX_INDEXTYPE KMaxJumpTableIndex = OMX_IndexConfigTimeClientStartTime; + +const CClockSupervisor::FunctionPtr CClockSupervisor::iJumpTable[] = + { + /*OMX_IndexConfigTimeScale*/ &CClockSupervisor::HandleGetSetTimeScale ,// requires buffers + /*OMX_IndexConfigTimeClockState*/ &CClockSupervisor::HandleGetSetClockState ,// requires buffers + /*OMX_IndexConfigTimeActiveRefClock*/ &CClockSupervisor::HandleGetSetActiveRefClock , + /*OMX_IndexConfigTimeCurrentMediaTime*/ &CClockSupervisor::HandleQueryCurrentMediaTime , + /*OMX_IndexConfigTimeCurrentWallTime*/ &CClockSupervisor::HandleQueryCurrentWallTime , + /*OMX_IndexConfigTimeCurrentAudioReference*/ &CClockSupervisor::HandleUpdateAudioReference , + /*OMX_IndexConfigTimeCurrentVideoReference*/ &CClockSupervisor::HandleUpdateVideoReference , + /*OMX_IndexConfigTimeMediaTimeRequest*/ &CClockSupervisor::HandleSubmitMediaTimeRequest,// requires buffers + /*OMX_IndexConfigTimeClientStartTime*/ &CClockSupervisor::HandleSetPortClientStartTime, + }; + +#ifdef _DEBUG +#define CHECK_DEBUG() DbgCheck() +#else +#define CHECK_DEBUG() +#endif + + +////////////// + + +/** + * Euclid's algorithm. + * Returns the largest common factor of aX and aY. + */ +static TInt LargestCommonFactor(TInt aX, TInt aY) + { + // based on knowledge that lcf(x,0)=x, lcf(x,y)=lcf(y,x) and lcf(x,y)=lcf(y,x%y) + while(aX != 0) + { + aY %= aX; + if(aY == 0) + { + return aX; + } + aX %= aY; + } + return aY; + } + + +/** + * + * + */ +CClockSupervisor* CClockSupervisor::NewL(COmxILClockProcessingFunction& aProcessingFunction) + { + CClockSupervisor* self = new (ELeave) CClockSupervisor(aProcessingFunction); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + +/** + * + * + */ +void CClockSupervisor::ConstructL() + { + // calculate tick frequency + // +#ifdef USE_FASTCOUNTER + TInt frequency; // tick frequency in Hz + User::LeaveIfError(HAL::Get(HAL::EFastCounterFrequency, frequency)); + // conversion factor from ticks into microseconds + // using a fraction in integer arithmetic + iMicroConvNum = 1000000; + iMicroConvDen = frequency; + TInt countsUp; + User::LeaveIfError(HAL::Get(HAL::EFastCounterCountsUp, countsUp)); + iSystemClockReversed = !countsUp; +#elif defined(USE_NTICKCOUNT) + User::LeaveIfError(HAL::Get(HAL::ENanoTickPeriod, iMicroConvNum)); // tick period in microseconds + iMicroConvDen = 1; +#else +#error +#endif + + // divide out any common factor to reduce chance of overflow + TInt factor = LargestCommonFactor(iMicroConvNum, iMicroConvDen); + iMicroConvNum /= factor; + iMicroConvDen /= factor; + + // wraparound time in microseconds is 2^32 * iMicroConv + // take the heartbeat interval as half of this (i.e shift left by 31 places) to ensure we wake up often enough to implement the carry properly + TUint64 interval = (static_cast(iMicroConvNum) << 31) / iMicroConvDen; + if (interval > KMaxTInt32) + { + iHeartbeatTimerInterval = KMaxTInt32; + } + else + { + iHeartbeatTimerInterval = interval; + } + + // if denominator is a power of 2, use shift instead + // (shifting is faster than division) + if(iMicroConvDen & (iMicroConvDen - 1) == 0) + { + // PRECONDITION: iMicroConvDen = 2^n, iMicroConvShift = 0 + // POSTCONDITION: iMicroConvDen = 1, iMicroConvShift = n + while(iMicroConvDen >= 2) + { + iMicroConvDen >>= 1; + iMicroConvShift++; + } + } + + // Create the locks + User::LeaveIfError(iQueMutex.CreateLocal()); + + // Create the memory block of empty Time requests that make up the free list. + iRequestBlock = new(ELeave) TMediaRequest [iMaxRequests]; + + // zero the fields for peace of mind + Mem::FillZ(iRequestBlock, (sizeof(TMediaRequest)*iMaxRequests)); + + // Initialise the free list + for(TInt requestIndex = 0; requestIndex < iMaxRequests; requestIndex++) + { + // Add all free items with delta 0 + iFreeRequestQue.Add(iRequestBlock+requestIndex, static_cast(0)); + } + + iStartTimes.ReserveL(KNumPorts); + for(TInt portIndex = 0; portIndex < KNumPorts; portIndex++) + { + iStartTimes.Append(0); + } + + __ASSERT_DEBUG(iMaxRequests == iFreeRequestQue.Count(), Panic(ERequestQueueCorrupt)); + + // Create the timer consumer thread + TBuf<19> threadName; + threadName.Format(_L("OmxILClock@%08X"), this); + + // Timer created on creation of the thread as it is thread relative + User::LeaveIfError(iThread.Create(threadName, ThreadEntryPoint, KThreadStackSize, NULL, this)); + + // High priority thread + iThread.SetPriority(EPriorityRealTime); + // start the thread and wait for it to create the timer + TRequestStatus status; + iThread.Rendezvous(status); + iThread.Resume(); + iThreadStarted = ETrue; + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + } + + +/** + * + * + */ +CClockSupervisor::CClockSupervisor(COmxILClockProcessingFunction& aProcessingFunction) +: iActiveRefClock(OMX_TIME_RefClockAudio), + iMaxRequests(KMaxRequests), + iProcessingFunction(aProcessingFunction) + { + // compile time assertion that fields requiring atomic access are + // 4-byte aligned + __ASSERT_COMPILE((_FOFF(CClockSupervisor, iWallTicks) & 3) == 0); + + // run time assertion that class itself is allocated on a 4-byte boundary + __ASSERT_ALWAYS((reinterpret_cast(this) & 3) == 0, Panic(EBadAlignment)); + + iMediaClockState.nSize = sizeof(iMediaClockState); + iMediaClockState.nVersion = TOmxILSpecVersion(); + iMediaClockState.eState = OMX_TIME_ClockStateStopped; + + iCancelStatus = KRequestPending; + } + +/** + * + * + */ +CClockSupervisor::~CClockSupervisor() + { + // signal the timing thread and wait for it to terminate + if(iThreadStarted) + { + iThreadRunning = EFalse; + TRequestStatus logonStatus; + iThread.Logon(logonStatus); + // Want the thread to terminate ASAP instead of waiting for a media + // time request completion or a wall time heartbeat. Can't cancel the + // timer without duplicating the handle, and that gives + // KErrPermissionDenied. So we use a second TRequestStatus to wake the + // timing thread before the timer completes. + TRequestStatus* cancelStatus = &iCancelStatus; + iThread.RequestComplete(cancelStatus, KErrCancel); + User::WaitForRequest(logonStatus); + } + + if (iRequestBlock) + { + delete[] iRequestBlock; + iRequestBlock = NULL; + } + + iStartTimes.Close(); + iThread.Close(); + iQueMutex.Close(); + } + + +/** + * Timing thread entry point. + */ +TInt CClockSupervisor::ThreadEntryPoint(TAny* aPtr) + { + CClockSupervisor& supervisor = *static_cast(aPtr); + supervisor.iThreadRunning = ETrue; + CTrapCleanup* cleanup = CTrapCleanup::New(); + if(cleanup == NULL) + { + return KErrNoMemory; + } + + // Delegate to object's clock timing routine + TRAPD(err, supervisor.RunTimingThreadL()); + delete cleanup; + return err; + } + + +/** + * + * + */ +void CClockSupervisor::RunTimingThreadL() + { + // Create the timer (thread relative) + User::LeaveIfError(iTimer.CreateLocal()); + // rendezvous with the creating thread as it must be blocked until the timer is created + iThread.Rendezvous(KErrNone); + + // Start the timer loop to enable us to wake up on heart beat or requests + TimerLoop(); + + iTimer.Close(); + } + +/** + * Services media time requests. + */ +void CClockSupervisor::TimerLoop() + { + // Here we are only interested in setting the timer for a request timeout + // or the heart beat. States are taken care of in ConsumeRequests(). + // On shutdown the flag iThreadRunning is set to EFalse and the timer + // completed with KErrCancel or something other than KRequestPending + // Therefore we no longer try to get another request. + + TInt measuredTimerOverhead = KTimerQuantization; + TRequestStatus timerStatus; + + while(iThreadRunning) + { + // Call our consumer function + TInt timeout = ConsumeRequests(); + + // round down sleep based on overhead and quantization of timers + timeout -= timeout % KTimerQuantization; + timeout -= measuredTimerOverhead; + timeout -= KMinTimerOverhead; + + // limit sleep by the heartbeat interval + if(timeout > iHeartbeatTimerInterval) + { + timeout = iHeartbeatTimerInterval; + } + + // Perhaps timeout not positive from ConsumeRequests(), or due to + // rounding down it is no longer positive. In this case we may spin, so + // be careful when setting KTimerQuantization, KMinTimerOverhead and + // KRequestDeltaLimit + if(timeout <= 0) + { + continue; + } + + iQueMutex.Wait(); + TInt64 actualSleepTime = WallTime(); + iQueMutex.Signal(); + iTimer.HighRes(timerStatus, timeout); + // can't cancel iTimer from another thread, instead we use a second + // TRequestStatus to wake up early, then we can cancel the timer in + // this thread. + User::WaitForRequest(timerStatus, iCancelStatus); + + if(iCancelStatus.Int() != KRequestPending) + { + iTimer.Cancel(); + iCancelStatus = KRequestPending; + } + else + { + // update measuredTimerOverhead + iQueMutex.Wait(); + actualSleepTime = WallTime() - actualSleepTime; + iQueMutex.Signal(); + TInt sleepOverhead = (TInt) (actualSleepTime - timeout); + + /* Dampen adjustments to measuredTimerOverhead + * + * measuredTimerOverhead = max(sleepOverhead, + * avg(measuredTimerOverhead, sleepOverhead)) + * + * i.e. immediate increase, gradual decrease + */ + measuredTimerOverhead = (measuredTimerOverhead + sleepOverhead) >> 1; + if(measuredTimerOverhead < sleepOverhead) + { + measuredTimerOverhead = sleepOverhead; + } + } + } + DEBUG_PRINTF(_L("Clock thread shutting down...")); + } + + +/** + * Update the wall clock with the system ticks + * + */ +void CClockSupervisor::UpdateWallTicks() + { + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + TUint32 oldLowPart(I64LOW(iWallTicks)); // save lower 32-bits +#ifdef USE_FASTCOUNTER + // Gets the fast iCounter. + // This is the current value of the machine's high resolution timer. + // If a high resolution timer is not available, it uses the millisecond timer instead. + // The freqency of this iCounter can be determined by reading the HAL attribute EFastCounterFrequency. + TUint32 newLowPart(User::FastCounter()); // set lower 32-bits + if(iSystemClockReversed) + { + newLowPart = -newLowPart; + } +#elif defined(USE_NTICKCOUNT) + TUint32 newLowPart(User::NTickCount()); // set lower 32-bits +#else +#error +#endif + TUint32 newHighPart(I64HIGH(iWallTicks)); // save upper 32-bits + + // note: did not use LockedInc() here because: + // We need a critical section to capture the system time and update the wall clock + // at the same time. + // The wall clock is unsigned. + // LockedInc doesn't stop a preemption directly after the test, only during the inc + if (newLowPart < oldLowPart) + { + newHighPart++; + } + + iWallTicks = MAKE_TUINT64(newHighPart,newLowPart); + } + + +/** + * Returns the current wall time in microseconds. + */ +TInt64 CClockSupervisor::WallTime() + { + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + UpdateWallTicks(); + + // if power of two use shift (faster than division) + if (iMicroConvDen == 1) + { + return iWallTicks * iMicroConvNum >> iMicroConvShift; + } + return iWallTicks * iMicroConvNum / iMicroConvDen; + } + +/** + * + * + */ +TBool CClockSupervisor::AllStartTimesReported () + { + return !(static_cast(iMediaClockState.nWaitMask)); + } + +/** + * Called when timer expires or is cancelled. + * + */ +TInt CClockSupervisor::ConsumeRequests() + { + // Events are consumed here only in the Running state. + // Waiting events, State change events, etc. are handled elsewhere. + // These are called broadcast events and have a higher priority + // than the request events requested by the clients. + // This function is called by the TimerLoop only. + + // IMPORTANT: every code path through here must result in a call to UpdateWallTicks to ensure + // that 64-bit time is updated correctly (heartbeat interval makes sure ConsumeRequests is called + // often enough). + + // Our event loop - return if stopped but not for pause (keep heart beat going) + if(OMX_TIME_ClockStateStopped != iMediaClockState.eState) + { + // Acquire the mutex + iQueMutex.Wait(); + + // this can be entered from the timer on heartbeat or on a call to a state change. + // Don't want this to happen on a state change! + if (OMX_TIME_ClockStateWaitingForStartTime == iMediaClockState.eState) + { + // We need to check if all clients have reported their start times + if (!AllStartTimesReported()) + { + // Not all reported as yet... + UpdateWallTicks(); + + // Release the mutex + iQueMutex.Signal(); + + // wait until they have! keep heat beat going in case it takes looooong! + return iHeartbeatTimerInterval; + } + else + { + // depending on scale decide which start time to use + CalculateStartTime(); + + // no need to check error, we are in the waiting state + DoTransitionToRunningState(); + + // Now just go and send the iNext request! + } + } + + /////////////////////////////// + // There is a request pending !!! + // + + // check the timeout period against the wall clock to determine if this is a heart beat + + if(iMtc.iScaleQ16 == 0) + { + // do not pop front of the queue because we are effectively paused + UpdateWallTicks(); + + // Release the mutex + iQueMutex.Signal(); + + return iHeartbeatTimerInterval; + } + + // try to pop the next pending request + TInt64 delta; + TBool present = iPendingRequestQue.FirstDelta(delta); + + // Is there nothing there? + if (!present) + { + // Nothing on the queue + // Here because of heart beat + UpdateWallTicks(); + + // Release the mutex + iQueMutex.Signal(); + + return iHeartbeatTimerInterval; + } + + // update the delta against clock + delta -= WallTime(); + + // Some time to go before head of queue should be serviced? + if (delta > KMinTimerOverhead) + { + // Release the mutex + iQueMutex.Signal(); + + return delta; + } + + // Request timed out, delta expired! + // Fulfill the request + TMediaRequest* request = iPendingRequestQue.RemoveFirst(); + + // Acquire fulfillment buffer for a specific port + OMX_BUFFERHEADERTYPE* buffer = iProcessingFunction.AcquireBuffer(request->iPortIndex); + if(buffer == NULL) + { + // starved of buffers! + DEBUG_PRINTF(_L("ConsumeRequests starved of buffers, transitioning to OMX_StateInvalid")); + iThreadRunning = EFalse; + iQueMutex.Signal(); + iProcessingFunction.InvalidateComponent(); + return 0; + } + OMX_TIME_MEDIATIMETYPE* mT = reinterpret_cast(buffer->pBuffer); + buffer->nOffset = 0; + buffer->nFilledLen = sizeof(OMX_TIME_MEDIATIMETYPE); + + mT->nSize = sizeof(OMX_TIME_MEDIATIMETYPE); + mT->nVersion = TOmxILSpecVersion(); + mT->eUpdateType = OMX_TIME_UpdateRequestFulfillment; + mT->nClientPrivate = reinterpret_cast(request->iClientPrivate); + mT->xScale = iMtc.iScaleQ16; + mT->eState = OMX_TIME_ClockStateRunning; + mT->nMediaTimestamp = request->iMediaTime; + mT->nWallTimeAtMediaTime = request->iTriggerWallTime + request->iOffset; + mT->nOffset = mT->nWallTimeAtMediaTime - WallTime(); // could be waiting on buffer b4 this! + +#ifdef _OMXIL_COMMON_DEBUG_TRACING_ON + DEBUG_PRINTF(_L8("CLOCK::ConsumeRequest*******************************OMX_TIME_UpdateRequestFulfillment***VS2")); + TTime t; + t.HomeTime(); + DEBUG_PRINTF2(_L8("CLOCK::ConsumeRequest : t.HomeTime() = %ld"), t.Int64()); + DEBUG_PRINTF2(_L8("CLOCK::ConsumeRequest : Buffer = 0x%X"), mT->nClientPrivate); + DEBUG_PRINTF2(_L8("CLOCK::ConsumeRequest : mT->nMediaTimestamp = %ld"), mT->nMediaTimestamp); +#endif + + iProcessingFunction.SendBuffer(buffer); + + // clear the delta on this now free request + request->iTriggerWallTime = 0; + + // Add element back to the free pool + iFreeRequestQue.Add(request, 0); + + // Release the mutex + iQueMutex.Signal(); + + // Update delta for next request. + // NOTE: we do not know if scale change or not so when we re-enter here + // we should recalculate the delta above and perform the appropriate action. + return 0; + } + else + { + // clock is stopped - sleep for the heartbeat interval + return iHeartbeatTimerInterval; + } + } + +/** + * + * + */ +OMX_ERRORTYPE CClockSupervisor::ProduceRequest(OMX_INDEXTYPE aIndex, TEntryPoint aEntryPoint, TAny* aPassedStructPtr) + { + // Range checking on parameter/config index + if(aIndex < KMinJumpTableIndex || aIndex > KMaxJumpTableIndex) + { + return OMX_ErrorUnsupportedIndex; + } + // Note also that certain combinations on Get/Set within the supported range are unsupported. + // This is left to the function in the jump table. + + // Acquire the mutex + iQueMutex.Wait(); + + // Index the routing table for the correct handler + FunctionPtr jumpTableFptr = iJumpTable[aIndex-KMinJumpTableIndex]; + OMX_ERRORTYPE ret = (this->*jumpTableFptr)(aEntryPoint, aPassedStructPtr); + + // Release the mutex + iQueMutex.Signal(); + + return ret; + } + + +/** + * According to scale, choose the earliest start time among the set of client + * start times. For forward play this is the minimum, for reverse play this is + * the maximum. + */ +void CClockSupervisor::CalculateStartTime() + { + // The nWaitMask field of the Media clock state is a bit mask specifying the client + // components that the clock component will wait on in the + // OMX_TIME_ClockStateWaitingForStartTime state. Bit masks are defined + // as OMX_CLOCKPORT0 through OMX_CLOCKPORT7. + + // Based on scale locate the minimum or maximum start times of all clients + TInt64 startTime; + + if (iMtc.iScaleQ16 >= 0) + { + startTime = KMaxTInt64; + + // choose minimum + for (TInt portIndex = 0; portIndex < iStartTimes.Count(); portIndex++) + { + if(iStartTimesSet & (1 << portIndex)) + { + startTime = Min(startTime, iStartTimes[portIndex]); + } + } + } + else + { + startTime = KMinTInt64; + + // choose maximum + for (TInt portIndex = 0; portIndex < iStartTimes.Count(); portIndex++) + { + if(iStartTimesSet & (1 << portIndex)) + { + startTime = Max(startTime, iStartTimes[portIndex]); + } + } + } + + // adjust the media time to the new start time + UpdateMediaTime(startTime); + } + + +/** + * Perform actions related to placing the clock into the Stopped state + */ +void CClockSupervisor::DoTransitionToStoppedState() + { + // OMX_TIME_ClockStateStopped: Immediately stop the media clock, clear all + // pending media time requests, clear all client start times, and transition to the + // stopped state. This transition is valid from all other states. + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + // clear any current start time + iMediaClockState.nStartTime = 0; + iMediaClockState.nWaitMask = 0; + + // clear client start times + for(TInt index = 0, count = iStartTimes.Count(); index < count; index++) + { + iStartTimes[index] = 0; + } + iStartTimesSet = 0; + + // clear all pending requests, placing them back on the free queue + while (!iPendingRequestQue.IsEmpty()) + { + TMediaRequest* request = iPendingRequestQue.RemoveFirst(); + + // clear the delta on this now free request + request->iTriggerWallTime = 0; + + iFreeRequestQue.Add(request,0); + } + + TInt64 wallTimeNow = WallTime(); + + // if clock was previously running, stop the media time at the present value + if(iMediaClockState.eState == OMX_TIME_ClockStateRunning) + { + TInt64 mediaTimeNow = ((wallTimeNow - iMtc.iWallTimeBase) * iMtc.iScaleQ16 >> 16) + iMtc.iMediaTimeBase; + iMtc.iWallTimeBase = wallTimeNow; + iMtc.iMediaTimeBase = mediaTimeNow; + } + + // Indicate stopped state + iMediaClockState.eState = OMX_TIME_ClockStateStopped; + + // Indicate to clients a state change + OMX_TIME_MEDIATIMETYPE update; + update.nSize = sizeof(OMX_TIME_MEDIATIMETYPE); + update.nVersion = TOmxILSpecVersion(); + update.nClientPrivate = NULL; + update.eUpdateType = OMX_TIME_UpdateClockStateChanged; + update.xScale = iMtc.iScaleQ16; + update.eState = OMX_TIME_ClockStateStopped; + update.nMediaTimestamp = iMtc.iMediaTimeBase; + update.nWallTimeAtMediaTime = wallTimeNow; + update.nOffset = 0; + + BroadcastUpdate(update); + } + + +/** + * Perform actions related to placing the clock into the Running state + */ +void CClockSupervisor::DoTransitionToRunningState() + { + // OMX_TIME_ClockStateRunning: Immediately start the media clock using the given + // start time and offset, and transition to the running state. This transition is valid from + // all other states. + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + // if we transistioned from stopped to running then start time will be cleared. + // we only set it on transition from waiting to running + // this is enforced not by a check but by logic flow! + // If this is not the case then start time should have been cleared and we can start now! + + // Indicate running state + iMediaClockState.eState = OMX_TIME_ClockStateRunning; + + // Indicate to clients a state change + OMX_TIME_MEDIATIMETYPE update; + update.nSize = sizeof(OMX_TIME_MEDIATIMETYPE); + update.nVersion = TOmxILSpecVersion(); + update.nClientPrivate = NULL; + update.eUpdateType = OMX_TIME_UpdateClockStateChanged; + update.xScale = iMtc.iScaleQ16; + update.eState = iMediaClockState.eState; + update.nMediaTimestamp = iMtc.iMediaTimeBase; + update.nWallTimeAtMediaTime = iMtc.iWallTimeBase; + update.nOffset = 0; + + BroadcastUpdate(update); + } + +/** + * Perform actions related to placing the clock into the Waiting state + */ +void CClockSupervisor::DoTransitionToWaitingState(OMX_U32 nWaitMask) + { + // OMX_TIME_WaitingForStartTime: Transition immediately to the waiting state, wait + // for all clients specified in nWaitMask to report their start time, start the media clock + // using the minimum of all client start times and transition to + // OMX_TIME_ClockStateRunning. This transition is only valid from the + // OMX_TIME_ClockStateStopped state. + // (*Added*) If in the backwards direction start the media clock using the maximum + // of all client start times. + + // validity of state transition has been checked in HandleGetSetClockState() + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + iMediaClockState.eState = OMX_TIME_ClockStateWaitingForStartTime; + + // Start times set in calling function HandleClientStartTime() + + // Remember all clients that need to report their start times + iMediaClockState.nWaitMask = nWaitMask; + + // Indicate to clients a state change + OMX_TIME_MEDIATIMETYPE update; + update.nSize = sizeof(OMX_TIME_MEDIATIMETYPE); + update.nVersion = TOmxILSpecVersion(); + update.nClientPrivate = NULL; + update.eUpdateType = OMX_TIME_UpdateClockStateChanged; + update.xScale = iMtc.iScaleQ16; + update.eState = OMX_TIME_ClockStateWaitingForStartTime; + update.nMediaTimestamp = iMtc.iMediaTimeBase; + update.nWallTimeAtMediaTime = WallTime(); + update.nOffset = 0; + + BroadcastUpdate(update); + } + + + /** + * Update the wall clock with the system ticks + * + */ + OMX_ERRORTYPE CClockSupervisor::HandleSubmitMediaTimeRequest(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + // A client requests the transmission of a particular timestamp via OMX_SetConfig on its + // clock port using the OMX_IndexConfigTimeMediaTimeRequest configuration + // and structure OMX_TIME_CONFIG_MEDIATIMEREQUESTTYPE + + // OMX_GetConfig doesn't make any sense for MediaTimeRequest + if(aEntry == EGetConfig) + { + return OMX_ErrorUnsupportedIndex; + } + + TMediaRequest *request = iFreeRequestQue.RemoveFirst(); + if(request == NULL) + { + // too many pending requests! + return OMX_ErrorInsufficientResources; + } + + OMX_TIME_CONFIG_MEDIATIMEREQUESTTYPE* rT = static_cast(aPassedStructPtr); + + request->iMediaTime = rT->nMediaTimestamp; + request->iOffset = rT->nOffset; + request->iTriggerWallTime = + ((rT->nMediaTimestamp - iMtc.iMediaTimeBase) * iMtc.iInverseScaleQ16 >> 16) + + iMtc.iWallTimeBase - rT->nOffset; + request->iPortIndex = rT->nPortIndex; + request->iClientPrivate = rT->pClientPrivate; + + TInt64 prevHeadTime(0); + TBool nonEmpty = iPendingRequestQue.FirstDelta(prevHeadTime); + iPendingRequestQue.Add(request, request->iTriggerWallTime); + // Wake the thread immediately if head of queue now will complete sooner + // than it would have previously, or if the queue was empty previously. + // This causes the timing thread to recalculate the sleep time. + if(!nonEmpty || prevHeadTime > request->iTriggerWallTime) + { + TRequestStatus* status = &iCancelStatus; + iThread.RequestComplete(status, KErrCancel); + } + return OMX_ErrorNone; + } + + + /** + * + * + */ + OMX_ERRORTYPE CClockSupervisor::HandleQueryCurrentWallTime(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // An IL client may query the current wall time via OMX_GetConfig on OMX_IndexConfigTimeCurrentWallTime. + // A client may obtain the current wall time, which is obtained via OMX_GetConfig on + // OMX_IndexConfigTimeCurrentWallTime. + + // OMX_SetConfig doesn't make sense for wall time + if(aEntry == ESetConfig) + { + return OMX_ErrorUnsupportedIndex; + } + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + OMX_TIME_CONFIG_TIMESTAMPTYPE& wallTs = *static_cast(aPassedStructPtr); + wallTs.nTimestamp = WallTime(); + return OMX_ErrorNone; + } + + +/** + * Calculates and returns the current media time. + */ +OMX_ERRORTYPE CClockSupervisor::HandleQueryCurrentMediaTime(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // The clock component can be queried for the current media clock time using + // OMX_GetConfig with the read-only index OMX_IndexConfigTimeCurrentMediaTime and structure + // OMX_TIME_CONFIG_TIMESTAMPTYPE. + + // OMX_SetConfig cannot be used for Media Time (audio or video reference updates are used instead) + if(aEntry == ESetConfig) + { + return OMX_ErrorUnsupportedIndex; + } + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + OMX_TIME_CONFIG_TIMESTAMPTYPE* ts = static_cast(aPassedStructPtr); + if(iMediaClockState.eState == OMX_TIME_ClockStateRunning) + { + ts->nTimestamp = ((WallTime() - iMtc.iWallTimeBase) * iMtc.iScaleQ16 >> 16) + iMtc.iMediaTimeBase; + } + else + { + ts->nTimestamp = iMtc.iMediaTimeBase; + } + return OMX_ErrorNone; + } + +/** + * An external component is providing an Audio reference time update. + */ +OMX_ERRORTYPE CClockSupervisor::HandleUpdateAudioReference(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // The clock component can accept an audio reference clock. + // The reference clock tracks the media time at its associated component + // (i.e., the timestamp of the data currently being processed at that component) + // and provides periodic references to the clock component via OMX_SetConfig + // using OMX_IndexConfigTimeCurrentAudioReference, and structure OMX_TIME_CONFIG_TIMESTAMPTYPE + // + // When the clock component receives a reference, it updates its internally maintained + // media time with the reference. This action synchronizes the clock component with the + // component that is providing the reference clock. + + // OMX_GetConfig not supported on reference time as it will generally be + // some arbitary time in the past + if(aEntry == EGetConfig) + { + return OMX_ErrorUnsupportedIndex; + } + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + if(iActiveRefClock == OMX_TIME_RefClockAudio) + { + OMX_TIME_CONFIG_TIMESTAMPTYPE* ts = static_cast(aPassedStructPtr); + UpdateMediaTime(ts->nTimestamp); + } + + return OMX_ErrorNone; + } + +/** + * An external component is providing a Video reference time update. + */ +OMX_ERRORTYPE CClockSupervisor::HandleUpdateVideoReference(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // The clock component can accept a video reference clock. + // The reference clock tracks the media time at its associated component + // (i.e., the timestamp of the data currently being processed at that component) + // and provides periodic references to the clock component via OMX_SetConfig + // using OMX_IndexConfigTimeCurrentVideoReference, and structure OMX_TIME_CONFIG_TIMESTAMPTYPE + // + // When the clock component receives a reference, it updates its internally maintained + // media time with the reference. This action synchronizes the clock component with the + // component that is providing the reference clock. + + // OMX_GetConfig not supported on reference time as it will generally be + // some arbitary time in the past + if(aEntry == EGetConfig) + { + return OMX_ErrorUnsupportedIndex; + } + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + if(iActiveRefClock == OMX_TIME_RefClockVideo) + { + OMX_TIME_CONFIG_TIMESTAMPTYPE* ts = static_cast(aPassedStructPtr); + UpdateMediaTime(ts->nTimestamp); + } + + return OMX_ErrorNone; + } + + /** + * + * + */ + OMX_ERRORTYPE CClockSupervisor::HandleSetPortClientStartTime(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + // When a clock component client receives a buffer with flag OMX_BUFFERFLAG_STARTTIME set, + // it performs an OMX_SetConfig call with OMX_IndexConfigTimeClientStartTime + // on the clock component that is sending the buffer’s timestamp. + // The transmission of the start time informs the clock component that the client’s stream + // is ready for presentation and the timestamp of the first data to be presented. + // + // If the IL client requests a transition to OMX_TIME_ClockStateWaitingForStartTime, it + // designates which clock component clients to wait for. The clock component then waits + // for these clients to send their start times via the + // OMX_IndexConfigTimeClientStartTime configuration. Once all required + // clients have responded, the clock component starts the media clock using the earliest + // client start time. + // + // When a client is sent a start time (i.e., the timestamp of a buffer marked with the + // OMX_BUFFERFLAG_STARTTIME flag ), it sends the start time to the clock component + // via OMX_SetConfig on OMX_IndexConfigTimeClientStartTime. This + // action communicates to the clock component the following information about the client’s + // data stream: + // - The stream is ready. + // - The starting timestamp of the stream + + // TODO Perhaps OMX_GetConfig can be done for client start time, after all + // the start times on each port have been stored. But for now this is not + // supported. + if(aEntry == EGetConfig) + { + return OMX_ErrorUnsupportedIndex; + } + + if(iMediaClockState.eState != OMX_TIME_ClockStateWaitingForStartTime) + { + return OMX_ErrorIncorrectStateOperation; + } + + OMX_TIME_CONFIG_TIMESTAMPTYPE* state = static_cast(aPassedStructPtr); + iMediaClockState.nWaitMask &= ~(1 << state->nPortIndex); // switch off bit + iStartTimesSet |= 1 << state->nPortIndex; // switch on bit + iStartTimes[state->nPortIndex] = state->nTimestamp; + + if(iMediaClockState.nWaitMask == 0) + { + // cancel timer so transition occurs immediately instead of waiting for a heartbeat + TRequestStatus* cancelStatus = &iCancelStatus; + iThread.RequestComplete(cancelStatus, KErrCancel); + } + + return OMX_ErrorNone; + } + +/** + * Sets the current media time to the specified value. + */ +void CClockSupervisor::UpdateMediaTime(TInt64 aMediaTime) + { +#ifdef _OMXIL_COMMON_DEBUG_TRACING_ON + OMX_TIME_CONFIG_TIMESTAMPTYPE ts; + HandleQueryCurrentMediaTime(EGetConfig, &ts); + DEBUG_PRINTF4(_L8("Clock::UpdateMediaTime=[%ld]currentmediaTime=<%d> MediaTime <%ld>"), ts.nTimestamp-aMediaTime,ts.nTimestamp, aMediaTime); +#endif // _OMXIL_COMMON_DEBUG_TRACING_ON + iMtc.iWallTimeBase = WallTime(); + iMtc.iMediaTimeBase = aMediaTime; + iPendingRequestQue.RecalculateAndReorder(iMtc); + + TRequestStatus* status = &iCancelStatus; + iThread.RequestComplete(status, KErrCancel); + } + + /** + * + * + */ + OMX_ERRORTYPE CClockSupervisor::HandleGetSetTimeScale(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // The IL client queries and sets the media clock’s scale via the + // OMX_IndexConfigTimeScale configuration, passing structure + // OMX_TIME_CONFIG_SCALETYPE + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + OMX_TIME_CONFIG_SCALETYPE* sT = static_cast(aPassedStructPtr); + + if (EGetConfig == aEntry) + { + sT->xScale = iMtc.iScaleQ16; + } + else + { // ESetConfig + + if(sT->xScale == iMtc.iScaleQ16) + { + // do not broadcast update if the scale changed to the same value + // IL client causes the scale change but only IL components receive the notification + return OMX_ErrorNone; + } + iMtc.SetScaleQ16(sT->xScale, WallTime()); + + // Reorder before sending notifications + iPendingRequestQue.RecalculateAndReorder(iMtc); + + // Indicate to clients a scale change + // The buffer payload is written here then copied to all clients' buffers + // It should be noted that as this is happening across all ports, time can pass + // making nMediaTimestamp and nWallTimeAtMediaTime inaccurate. Since at present we do + // not recover buffer exhaustion scenarios, it is assumed that the messaging time is short + // thus we do not recalculate the time. + + OMX_TIME_MEDIATIMETYPE update; + update.nSize = sizeof(OMX_TIME_MEDIATIMETYPE); + update.nVersion = TOmxILSpecVersion(); + update.nClientPrivate = NULL; + update.eUpdateType = OMX_TIME_UpdateScaleChanged; + update.xScale = iMtc.iScaleQ16; + update.eState = iMediaClockState.eState; + update.nMediaTimestamp = iMtc.iMediaTimeBase; + update.nWallTimeAtMediaTime = iMtc.iWallTimeBase; + update.nOffset = 0; + + BroadcastUpdate(update); + + // Signal the Timer thread as it may need to fulfill all requests on a direction change, + // of fulfill some requests as we have moved forward in time + TRequestStatus* cancelStatus = &iCancelStatus; + iThread.RequestComplete(cancelStatus, KErrCancel); + //****************************************** + } + return OMX_ErrorNone; + } + + + /** + * + * + */ + OMX_ERRORTYPE CClockSupervisor::HandleGetSetClockState(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // An OMX_GetConfig execution using index OMX_IndexConfigTimeClockState + // and structure OMX_TIME_CONFIG_CLOCKSTATETYPE queries the current clock state. + // An OMX_SetConfig execution using index OMX_IndexConfigTimeClockState + // and structure OMX_TIME_CONFIG_CLOCKSTATETYPE commands the clock + // component to transition to the given state, effectively providing the IL client a + // mechanism for starting and stopping the media clock. + // + // Upon receiving OMX_SetConfig from the IL client that requests a transition to the + // given state, the clock component will do the following: + // + // - OMX_TIME_ClockStateStopped: Immediately stop the media clock, clear all + // pending media time requests, clear and all client start times, and transition to the + // stopped state. This transition is valid from all other states. + // + // - OMX_TIME_ClockStateRunning: Immediately start the media clock using the given + // start time and offset, and transition to the running state. This transition is valid from + // all other states. + // + // - OMX_TIME_WaitingForStartTime: Transition immediately to the waiting state, wait + // for all clients specified in nWaitMask to report their start time, start the media clock + // using the minimum of all client start times and transition to + // OMX_TIME_ClockStateRunning. This transition is only valid from the + // OMX_TIME_ClockStateStopped state. + + // We should already have the mutex! + __ASSERT_DEBUG(iQueMutex.IsHeld(), Panic(EMutexUnheld)); + + OMX_ERRORTYPE ret = OMX_ErrorNone; + + OMX_TIME_CONFIG_CLOCKSTATETYPE* const &clkState + = static_cast(aPassedStructPtr); + + if (ESetConfig == aEntry) + { + if (iMediaClockState.eState == clkState->eState) + { + // Already in this state! + return OMX_ErrorSameState; + } + if (clkState->eState != OMX_TIME_ClockStateStopped && + clkState->eState != OMX_TIME_ClockStateWaitingForStartTime && + clkState->eState != OMX_TIME_ClockStateRunning) + { + return OMX_ErrorUnsupportedSetting; + } + + // need buffers to notify clock state changes, so require to be in Executing + if(!iProcessingFunction.IsExecuting()) + { + return OMX_ErrorIncorrectStateOperation; + } + + switch (clkState->eState) + { + case OMX_TIME_ClockStateStopped: + { + DoTransitionToStoppedState(); + break; + } + case OMX_TIME_ClockStateWaitingForStartTime: + { + // Can't go into this state from Running state + if (OMX_TIME_ClockStateRunning == iMediaClockState.eState) + { + ret = OMX_ErrorIncorrectStateTransition; + break; + } + // Waiting for no ports makes no sense. Also don't allow wait on ports we don't have. + if (clkState->nWaitMask == 0 || clkState->nWaitMask >= 1 << KNumPorts) + { + ret = OMX_ErrorUnsupportedSetting; + break; + } + DoTransitionToWaitingState(clkState->nWaitMask); + break; + } + case OMX_TIME_ClockStateRunning: + { + // set media time to that passed by the IL client + iMtc.iWallTimeBase = WallTime(); + iMtc.iMediaTimeBase = clkState->nStartTime; + // changed time base so pending trigger wall times may be different + iPendingRequestQue.RecalculateAndReorder(iMtc); + DoTransitionToRunningState(); + // wake the timer thread to reset timer or service pending updates + TRequestStatus* statusPtr = &iCancelStatus; + iThread.RequestComplete(statusPtr, KErrCancel); + + break; + } + // default condition already checked before Executing test + } + } + else + { + clkState->eState = iMediaClockState.eState; + } + + return ret; + } + + /** + * + * + */ + OMX_ERRORTYPE CClockSupervisor::HandleGetSetActiveRefClock(TEntryPoint aEntry, OMX_PTR aPassedStructPtr) + { + // The IL client controls which reference clock the clock component uses (if any) via the + // OMX_IndexConfigTimeActiveRefClock configuration and structure OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE + + // don't need the mutex here as just getting/setting one machine word + + OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE& ref = *static_cast(aPassedStructPtr); + + if (ESetConfig == aEntry) + { + if (ref.eClock != OMX_TIME_RefClockAudio && + ref.eClock != OMX_TIME_RefClockVideo && + ref.eClock != OMX_TIME_RefClockNone) + { + return OMX_ErrorUnsupportedSetting; + } + iActiveRefClock = ref.eClock; + } + else // EGetConfig + { + ref.eClock = iActiveRefClock; + } + return OMX_ErrorNone; + } + +void CClockSupervisor::BroadcastUpdate(const OMX_TIME_MEDIATIMETYPE& aUpdate) + { + // notify state change on all enabled ports + for(TInt portIndex = 0; portIndex < KNumPorts; portIndex++) + { + if(iProcessingFunction.PortEnabled(portIndex)) + { + OMX_BUFFERHEADERTYPE* buffer = iProcessingFunction.AcquireBuffer(portIndex); + if(buffer == NULL) + { + // starved of buffers! + iThreadRunning = EFalse; + iProcessingFunction.InvalidateComponent(); + return; + } + OMX_TIME_MEDIATIMETYPE& mT = *reinterpret_cast(buffer->pBuffer); + mT = aUpdate; + buffer->nOffset = 0; + buffer->nFilledLen = sizeof(OMX_TIME_MEDIATIMETYPE); + iProcessingFunction.SendBuffer(buffer); + } + } + } + +void CClockSupervisor::ReportClockThreadPanic() + { + iThreadRunning = EFalse; + iProcessingFunction.InvalidateComponent(); + } +/** + * Adjusts the media time scale. + * The wall/media time bases are updated so there is no instantaneous change to media time. + * iInverseScaleQ16 is also recalculated. + */ +void TMediaTimeContext::SetScaleQ16(TInt32 aScaleQ16, TInt64 aWallTimeNow) + { + TInt64 mediaTimeNow = ((aWallTimeNow - iWallTimeBase) * iScaleQ16 >> 16) + iMediaTimeBase; + iWallTimeBase = aWallTimeNow; + iMediaTimeBase = mediaTimeNow; + iScaleQ16 = aScaleQ16; + + // calculate inverse scale + // 1.0/scale in Q16 format becomes 2^32/scaleQ16 + // values of -1 and +1 will cause overflow and are clipped + // division by zero also yields KMaxTInt (2^31 - 1) + if(iScaleQ16 == 0 || iScaleQ16 == 1) + { + iInverseScaleQ16 = 0x7FFFFFFF; + } + else if(iScaleQ16 == -1) + { + iInverseScaleQ16 = 0x80000000; + } + else + { + iInverseScaleQ16 = static_cast(0x100000000LL / iScaleQ16); + } + } + + +TRequestDeltaQue::TRequestDeltaQue() + : iHead(0), + iCount(0) + { + // do nothing + } + + void TRequestDeltaQue::Add(TMediaRequest* aElement, TInt64 aDelta) + { + CHECK_DEBUG(); + + __ASSERT_DEBUG(((iHead == NULL && iCount == 0) || (iHead != NULL && iCount > 0)), + Panic(ERequestQueueCorrupt)); + + if (!iHead) + { + iHead = aElement; + aElement->iPrev = aElement; + aElement->iNext = aElement; + } + else // set the new element links + { + TBool front(EFalse); + TMediaRequest* item(NULL); + front = InsertBeforeFoundPosition (aDelta, item); + + if (front) + { + // insert infront BEFORE as we have the lesser delta + // set up the element + aElement->iPrev = item->iPrev; + aElement->iNext = item; + + // set up the item before in the list + item->iPrev->iNext = aElement; + + // set up the item after in the list + item->iPrev = aElement; + + // setup the new head + iHead = aElement; + } + else + { + // insert this element AFTER the item in the list + // set up the element + aElement->iPrev = item; + aElement->iNext = item->iNext; + + // set up the item before in the list + item->iNext = aElement; + + // set up the item after in the list + aElement->iNext->iPrev = aElement; + } + } + iCount++; + +#ifdef _OMXIL_COMMON_DEBUG_TRACING_ON + // print the trigger times in debug mode + DbgPrint(); +#endif + + CHECK_DEBUG(); + } + + TBool TRequestDeltaQue::InsertBeforeFoundPosition(TInt64 aDelta, TMediaRequest*& aItem) const + { + CHECK_DEBUG(); + + __ASSERT_DEBUG(((iHead == NULL && iCount == 0) || (iHead != NULL && iCount > 0)), + Panic(ERequestQueueCorrupt)); + + // search for the position where to insert + // and insert after this + + aItem = iHead->iPrev; // tail + + // start from the end and linearly work backwards + while (aItem->iTriggerWallTime > aDelta && aItem != iHead) + { + aItem = aItem->iPrev; + } + + // indicates that we insert before the item and not after it + if (aItem == iHead && aItem->iTriggerWallTime > aDelta) + { + return ETrue; + } + + // no CHECK_DEBUG required as this method is const + + return EFalse; + } + +/** + * If scale changes, the iTriggerWallTimes must be recalculated. + * + * In addition, because offset is not affected by scale, it is possible for + * the order of the elements to change. This event is assumed to be rare, and + * when it does occur we expect the list to remain 'roughly sorted' requiring + * few exchanges. + * + * So we choose ** bubble sort **. + * + * Time recalculation is merged with the first pass of bubble sort. Times are + * recalculated in the first iteration only. Swaps can occur as necessary in + * all iterations. We expect that in most cases there will only be one pass + * with no swaps. + * + * Note that bubble sort would be worst-case complexity if reversing the list + * due to a time direction change. In such cases future requests complete + * immediately (because they are now in the past). So we do not sort at at all + * in this case. + */ + void TRequestDeltaQue::RecalculateAndReorder(TMediaTimeContext& aMtc) + { + CHECK_DEBUG(); + + __ASSERT_DEBUG(((iHead == NULL && iCount == 0) || (iHead != NULL && iCount > 0)), + Panic(ERequestQueueCorrupt)); + + if(iCount == 0) + { + // nothing to do + return; + } + // note if there is 1 item there is no reorder but we do need to recalculate + + TBool swapped(EFalse); + TBool deltaCalculated(EFalse); + + do + { + // start from end of queue + swapped = EFalse; + TMediaRequest* item(iHead->iPrev); // tail + + if (!deltaCalculated) + { + // calculate the tails new delta + item->iTriggerWallTime = + ((item->iMediaTime - aMtc.iMediaTimeBase) * aMtc.iInverseScaleQ16 >> 16) + + aMtc.iWallTimeBase - item->iOffset; + } + + while (item != iHead) + { + TMediaRequest* swap = item->iPrev; + if (!deltaCalculated) + { + // recalculate the Prev item delta + swap->iTriggerWallTime = + ((swap->iMediaTime - aMtc.iMediaTimeBase) * aMtc.iInverseScaleQ16 >> 16) + + aMtc.iWallTimeBase - swap->iOffset; + } + + if (swap->iTriggerWallTime > item->iTriggerWallTime) + { + // switch (swap, item) for (item, swap) + item->Deque(); + item->AddBefore(swap); + if(swap == iHead) + { + iHead = item; + } + + swapped = ETrue; + } + else + { + // move along list + item = item->iPrev; + } + + } // while + + // after the first pass of the queue, all deltas calculated + deltaCalculated = ETrue; + + } while (swapped); + +#ifdef _OMXIL_COMMON_DEBUG_TRACING_ON + DbgPrint(); +#endif + + CHECK_DEBUG(); + } + + + TMediaRequest* TRequestDeltaQue::RemoveFirst() + { + CHECK_DEBUG(); + + __ASSERT_DEBUG(((iHead == NULL && iCount == 0) || (iHead != NULL && iCount > 0)), + Panic(ERequestQueueCorrupt)); + + TMediaRequest* item = NULL; + + // empty? + if (!iHead) + { + return NULL; + } + else + // only one element? + if (1 == iCount) + { + item = iHead; + iHead = NULL; + } + else + { + item = iHead; + item->iPrev->iNext = item->iNext; + item->iNext->iPrev = item->iPrev; + + // setup the new head + iHead = item->iNext; + } + + iCount--; + + CHECK_DEBUG(); + + return item; + } + + TBool TRequestDeltaQue::FirstDelta(TInt64& aDelta) const + { + CHECK_DEBUG(); + + __ASSERT_DEBUG(((iHead == NULL && iCount == 0) || (iHead != NULL && iCount > 0)), + Panic(ERequestQueueCorrupt)); + + if (!iHead) + { + return EFalse; + } + + aDelta = iHead->iTriggerWallTime; + return ETrue; + } + + TBool TRequestDeltaQue::IsEmpty() const + { + __ASSERT_DEBUG(((iHead == NULL && iCount == 0) || (iHead != NULL && iCount > 0)), + Panic(ERequestQueueCorrupt)); + + return (!iHead); + } + + TUint TRequestDeltaQue::Count() const + { + return iCount; + } + +void TMediaRequest::Deque() + { + iPrev->iNext = iNext; + iNext->iPrev = iPrev; + } + +void TMediaRequest::AddBefore(TMediaRequest* x) + { + x->iPrev->iNext = this; + iPrev = x->iPrev; + iNext = x; + x->iPrev = this; + } + +#ifdef _DEBUG + + /** + * Checks the linked list for consistency. + */ + void TRequestDeltaQue::DbgCheck() const + { + if(iCount == 0) + { + __ASSERT_DEBUG(iHead == NULL, Panic(ERequestQueueCorrupt)); + } + else + { + TMediaRequest* last = iHead; + TInt index = iCount - 1; + while(index-- > 0) + { + TMediaRequest* current = last->iNext; + __ASSERT_DEBUG(current->iPrev == last, Panic(ERequestQueueCorrupt)); + __ASSERT_DEBUG(current->iTriggerWallTime >= last->iTriggerWallTime, Panic(ERequestQueueUnordered)); + last = current; + } + __ASSERT_DEBUG(last->iNext == iHead, Panic(ERequestQueueCorrupt)); + __ASSERT_DEBUG(iHead->iPrev == last, Panic(ERequestQueueCorrupt)); + } + } + +#endif + +#ifdef _OMXIL_COMMON_DEBUG_TRACING_ON + +void TRequestDeltaQue::DbgPrint() const + { + TMediaRequest* x = iHead; + TBuf8<256> msg; + msg.Append(_L8("pending times: ")); + for(TInt index = 0; index < iCount; index++) + { + if(index > 0) + { + msg.Append(_L8(", ")); + } + msg.AppendFormat(_L8("%ld"), x->iTriggerWallTime); + x = x->iNext; + } + DEBUG_PRINTF(msg); + } + +#endif