sl@0: // Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // sl@0: sl@0: #include "videorenderer.h" sl@0: #include "rendererrelay.h" sl@0: #include "buffermanager.h" sl@0: #include "rendererutil.h" sl@0: #include "renderertimer.h" sl@0: #include "resourcefilereader.h" sl@0: #include "videoframebuffer.h" sl@0: sl@0: sl@0: _LIT(KVideoRendererThreadName, "VideoRendererThread"); sl@0: _LIT(KResourceFileName, "Z:\\resource\\videorenderer\\videorenderer.rsc"); sl@0: sl@0: const TThreadPriority KSubThreadPriority = EPriorityRealTime; sl@0: const TInt KSubThreadStackSize = KDefaultStackSize; // 0x2000 = 8k sl@0: sl@0: sl@0: sl@0: /** sl@0: Factory method. Creates a new video renderer instance. sl@0: sl@0: @param aObserver Video renderer observer sl@0: @param aTimed ETrue if CVideoRenderer should handle timing of buffer updates. sl@0: If ETrue CVideoRenderer launches its own high-priority thread to handle buffer update timing. If EFalse CVideoRenderer requires a CActiveScheduler to be present in calling thread. sl@0: @return A pointer to the newly created CVideoRenderer object. sl@0: */ sl@0: EXPORT_C CVideoRenderer* CVideoRenderer::NewL(MVideoRendererObserver& aObserver, TBool aTimed) sl@0: { sl@0: CVideoRenderer* self = new (ELeave) CVideoRenderer(aObserver, aTimed); sl@0: CleanupStack::PushL(self); sl@0: self->ConstructL(); sl@0: CleanupStack::Pop(self); sl@0: return self; sl@0: } sl@0: sl@0: /** sl@0: Private constructor sl@0: @internalComponent sl@0: */ sl@0: CVideoRenderer::CVideoRenderer(MVideoRendererObserver& aObserver, TBool aTimed) sl@0: :iObserver(aObserver), iTimed(aTimed) sl@0: { sl@0: } sl@0: sl@0: /** sl@0: Second phase constructor sl@0: @internalComponent sl@0: */ sl@0: void CVideoRenderer::ConstructL() sl@0: { sl@0: User::LeaveIfError(iSurfaceManager.Open()); sl@0: sl@0: User::LeaveIfError(iWsSession.Connect()); sl@0: sl@0: CResourceFileReader* reader = CResourceFileReader::NewLC(KResourceFileName); sl@0: reader->ReadSupportedFormatL(iSupportedFormat); sl@0: sl@0: if (iTimed) sl@0: { sl@0: // Create a high priority thread for timed mode sl@0: sl@0: // get timer info for timed mode sl@0: TInt64 defaultDelay; sl@0: TInt64 maxDelay; sl@0: reader->ReadTimerInfoL(defaultDelay, maxDelay); sl@0: sl@0: //Get a reference to this thread's heap sl@0: RHeap& thisHeap = User::Heap(); sl@0: sl@0: //Parameters to send to the sub thread sl@0: TThreadRelayParam param; sl@0: param.iObserver = &iObserver; sl@0: param.iThreadRelay = &iRendererRelay; // return pointer to allow direct calls sl@0: sl@0: //Get the id of this thread sl@0: RThread thisThread; sl@0: TThreadId thisThreadId = thisThread.Id(); sl@0: param.iMainThreadId = thisThreadId; sl@0: sl@0: //Get a request to signal for setup completion sl@0: TRequestStatus setupComplete = KRequestPending; sl@0: param.iSetupComplete = &setupComplete; sl@0: sl@0: //current time and the "this" pointer for a unique key sl@0: _LIT(KFormatString,"%S.%020Lu.%08X"); sl@0: TName threadName; sl@0: TTime now; sl@0: now.UniversalTime(); sl@0: threadName.Format(KFormatString, &KVideoRendererThreadName, now.Int64(), reinterpret_cast(this)); sl@0: sl@0: //Create a new thread using the same heap as this thread sl@0: TInt result = iRendererThread.Create(threadName, sl@0: ThreadCreateFunction, sl@0: KSubThreadStackSize, sl@0: &thisHeap, sl@0: ¶m); sl@0: User::LeaveIfError(result); sl@0: sl@0: //Run the thread under high priority sl@0: iRendererThread.SetPriority(KSubThreadPriority); sl@0: sl@0: //Wait for thread startup to complete sl@0: TRequestStatus threadStatus = KRequestPending; sl@0: iRendererThread.Logon(threadStatus); sl@0: sl@0: //Start the thread sl@0: iRendererThread.Resume(); sl@0: User::WaitForRequest(threadStatus, setupComplete); sl@0: if(threadStatus != KRequestPending) sl@0: { sl@0: //Thread creation failed sl@0: TInt reason = iRendererThread.ExitReason(); sl@0: DEBUGPRINT3(_L("Renderer thread died with type=%d, reason=%d"), iRendererThread.ExitType(), reason); sl@0: User::Leave(reason); sl@0: } sl@0: sl@0: // Thread creation was successfull sl@0: TInt error = iRendererThread.LogonCancel(threadStatus); sl@0: User::LeaveIfError(error); // There is no outstanding request sl@0: User::WaitForRequest(threadStatus); // Consume the signal sl@0: sl@0: __ASSERT_DEBUG(iRendererRelay != NULL, User::Panic(_L("CVR::ConstructL"), KErrCorrupt)); sl@0: iRendererRelay->SetRendererThread(&iRendererThread); sl@0: iRendererRelay->SetTimerInfo(defaultDelay, maxDelay); sl@0: sl@0: iThreadCreated = ETrue; sl@0: User::LeaveIfError(setupComplete.Int()); sl@0: sl@0: //Create a listener that will monitor the thread sl@0: iRendererThreadUndertaker = CThreadUndertaker::NewL(iRendererThread); sl@0: } sl@0: else sl@0: { sl@0: iRendererRelay = CRendererRelay::NewL(iObserver); sl@0: } sl@0: sl@0: CleanupStack::PopAndDestroy(reader); sl@0: } sl@0: sl@0: /** sl@0: Main thread entry point for the video renderer sub-thread. sl@0: Create a cleanup stack for the thread and process the codec sl@0: inside a trap for cleanup behaviour. sl@0: @internalComponent sl@0: sl@0: @param aPtr Parameters to be used for creating the thread. sl@0: @return The error code for thread termination. sl@0: */ sl@0: TInt CVideoRenderer::ThreadCreateFunction(TAny* aPtr) sl@0: { sl@0: TInt error = KErrNone; sl@0: sl@0: // Create a cleanup stack for the thread sl@0: CTrapCleanup* cleanupStack = CTrapCleanup::New(); sl@0: if (cleanupStack) sl@0: { sl@0: if(error == KErrNone) sl@0: { sl@0: TRAP(error, ThreadTrapFunctionL(aPtr)); sl@0: } sl@0: } sl@0: else sl@0: { sl@0: error = KErrNoMemory; sl@0: } sl@0: sl@0: delete cleanupStack; sl@0: return error; sl@0: } sl@0: sl@0: /** sl@0: Function for thread execution. Create an active scheduler for the thread sl@0: and start the function call listener. If the thread is successfully created signal sl@0: the main thread that the thread creation was successfull. sl@0: @internalComponent sl@0: sl@0: @param aPtr A pointer to a TThreadRelayParam object containing the startup parameters sl@0: for the thread. sl@0: */ sl@0: void CVideoRenderer::ThreadTrapFunctionL(TAny* aPtr) sl@0: { sl@0: //Create an active scheduler for the thread sl@0: CActiveScheduler* scheduler = new (ELeave) CActiveScheduler; sl@0: CleanupStack::PushL(scheduler); sl@0: CActiveScheduler::Install(scheduler); sl@0: sl@0: //Call a Factory function to create a CSubThreadRelay derived object sl@0: TThreadRelayParam* relayParam; sl@0: relayParam = static_cast(aPtr); sl@0: sl@0: CRendererThreadRelay* threadRelay = CRendererThreadRelay::NewL(*relayParam->iObserver, relayParam->iMainThreadId); sl@0: CleanupStack::PushL(threadRelay); sl@0: sl@0: //Send a pointer to the sub thread relay back to the main thread sl@0: *relayParam->iThreadRelay = threadRelay; sl@0: sl@0: threadRelay->Start(); sl@0: threadRelay->SignalSetupComplete(relayParam->iSetupComplete); sl@0: sl@0: CActiveScheduler::Start(); sl@0: sl@0: CleanupStack::PopAndDestroy(2,scheduler); //threadRelay, scheduler sl@0: } sl@0: sl@0: /** sl@0: Destructor sl@0: */ sl@0: EXPORT_C CVideoRenderer::~CVideoRenderer() sl@0: { sl@0: iWsSession.Close(); sl@0: iSupportedFormat.Close(); sl@0: sl@0: // first, stop all active objects that may use any buffers sl@0: if(iThreadCreated) sl@0: { sl@0: delete iRendererThreadUndertaker; sl@0: sl@0: // this also delete CRendererThreadRelay sl@0: TRequestStatus terminateRequest = KRequestPending; sl@0: iRendererRelay->Terminate(terminateRequest); sl@0: User::WaitForRequest(terminateRequest); sl@0: iThreadCreated = EFalse; sl@0: sl@0: iRendererThread.Close(); sl@0: } sl@0: else if (iTimed == EFalse) sl@0: { sl@0: delete iRendererRelay; sl@0: } sl@0: sl@0: // next, close surface if still open sl@0: if (iBufferManager) sl@0: { sl@0: delete iBufferManager; sl@0: iSurfaceManager.CloseSurface(iSurfaceId); sl@0: } sl@0: sl@0: // close the rest sl@0: iSurfaceManager.Close(); sl@0: } sl@0: sl@0: /** sl@0: Retrieves the list of supported surface formats from the renderer. sl@0: sl@0: @param aArray Supported video formats. The renderer will populate the array with supported formats. sl@0: @leave KErrNoMemory Out of memory sl@0: */ sl@0: EXPORT_C void CVideoRenderer::GetSupportedFormatsL(RArray& aArray) sl@0: { sl@0: aArray.Reset(); // first clear the old data sl@0: sl@0: TInt count = iSupportedFormat.Count(); sl@0: for (TInt i = 0; i < count; ++i) sl@0: { sl@0: aArray.AppendL(iSupportedFormat[i]); sl@0: } sl@0: } sl@0: sl@0: /** sl@0: Creates a new surface for video rendering and registers it with the windows server for all displays. sl@0: sl@0: @param aSize Surface size in pixels sl@0: @param aNumBuffers The minimum number of buffers required. The renderer can create a surface with more buffers. sl@0: @param aFormat Surface data format sl@0: @param aSurface Output: Surface ID for the new surface sl@0: sl@0: @leave KErrNoMemory Out of memory sl@0: @leave KErrNotSupported The requested parameters are not supported sl@0: @leave KErrInUse Too many video surfaces are already in use sl@0: */ sl@0: EXPORT_C void CVideoRenderer::CreateSurfaceL(const TSize& aSize, TInt aNumBuffers, const TUncompressedVideoFormat& aFormat, TSurfaceId& aSurface) sl@0: { sl@0: if (iBufferManager != NULL) sl@0: { sl@0: User::Leave(KErrInUse); sl@0: } sl@0: else if (aNumBuffers <= 0) sl@0: { sl@0: User::Leave(KErrNotSupported); sl@0: } sl@0: sl@0: RSurfaceManager::TSurfaceCreationAttributesBuf attribBuf; sl@0: RSurfaceManager::TSurfaceCreationAttributes& attrib = attribBuf(); sl@0: attrib.iSize = aSize; sl@0: attrib.iBuffers = aNumBuffers; sl@0: attrib.iPixelFormat = VideoRendererUtil::ConvertUncompressedVideoFormatToUidPixelFormatL(aFormat); sl@0: attrib.iStride = aSize.iWidth * VideoRendererUtil::BytesPerPixelL(attrib.iPixelFormat); sl@0: attrib.iOffsetToFirstBuffer = 0; sl@0: attrib.iAlignment = 4; sl@0: attrib.iContiguous = ETrue; sl@0: attrib.iMappable = ETrue; sl@0: sl@0: User::LeaveIfError(iSurfaceManager.CreateSurface(attribBuf, iSurfaceId)); sl@0: sl@0: // Push surfaceId to cleanup stack in case buffer manager constructor leave. sl@0: // SurfaceId needs to be pushed because the renderer state is inconsistent sl@0: // if buffer manager creation leave sl@0: CleanupReleasePushL(*this); sl@0: sl@0: RChunk chunk; sl@0: User::LeaveIfError(iSurfaceManager.MapSurface(iSurfaceId, chunk)); sl@0: CleanupClosePushL(chunk); sl@0: sl@0: RSurfaceManager::TInfoBuf infoBuf; sl@0: User::LeaveIfError(iSurfaceManager.SurfaceInfo(iSurfaceId, infoBuf)); sl@0: sl@0: // Register must take place after 'this' pushed onto cleanupstack just in case sl@0: // function leaves after registration sl@0: RegisterSurfaceL(); sl@0: sl@0: // prepare the renderer relay sl@0: RSurfaceManager::TSurfaceInfoV01 info = infoBuf(); sl@0: if (iTimed) sl@0: { sl@0: TRequestStatus updateRequest = KRequestPending; sl@0: TRequestStatus logonRequest = KRequestPending; sl@0: sl@0: // While a function call is in progress this thread is suspended sl@0: // and the undertaker will not catch panics, listen for these here sl@0: iRendererThread.Logon(logonRequest); sl@0: sl@0: // Send request to renderer thread sl@0: iRendererRelay->PrepareL(iSurfaceId, info.iBuffers, &updateRequest); sl@0: User::WaitForRequest(logonRequest, updateRequest); sl@0: sl@0: if(logonRequest != KRequestPending) sl@0: { sl@0: // renderer thread got panic from surface update session, so panic client sl@0: TInt reason = iRendererThread.ExitReason(); sl@0: TExitCategoryName category = iRendererThread.ExitCategory(); sl@0: User::Panic(category,reason); sl@0: } sl@0: sl@0: // Thread is still alive and well sl@0: iRendererThread.LogonCancel(logonRequest); sl@0: User::WaitForRequest(logonRequest); // Consume the signal sl@0: sl@0: // leave if memory allocation failed in renderer thread sl@0: User::LeaveIfError(updateRequest.Int()); sl@0: sl@0: __ASSERT_DEBUG(updateRequest != KRequestPending, User::Panic(_L("CVR::UpdateBuffer"), KErrCorrupt)); sl@0: } sl@0: else sl@0: { sl@0: iRendererRelay->PrepareL(iSurfaceId, info.iBuffers, NULL); sl@0: } sl@0: sl@0: // find the buffer offsets sl@0: RArray offsets; sl@0: CleanupClosePushL(offsets); sl@0: sl@0: TInt offsetInChunk = 0; sl@0: for (TInt i = 0; i < info.iBuffers; ++i) sl@0: { sl@0: User::LeaveIfError(iSurfaceManager.GetBufferOffset(iSurfaceId, i, offsetInChunk)); sl@0: offsets.AppendL(offsetInChunk); sl@0: } sl@0: sl@0: // finally, create buffer manager sl@0: iBufferManager = CRendererBufferManager::NewL(info, offsets, chunk, iTimed); sl@0: sl@0: CleanupStack::PopAndDestroy(&offsets); sl@0: CleanupStack::Pop(&chunk); sl@0: CleanupStack::Pop(this); // surfaceId sl@0: sl@0: aSurface = iSurfaceId; sl@0: iRendererRelay->SetBufferManager(iBufferManager); sl@0: sl@0: for (TInt i = 0; i < info.iBuffers; ++i) sl@0: { sl@0: // notify observer once for each buffer created sl@0: iObserver.MvroVideoBufferAvailable(); sl@0: } sl@0: } sl@0: sl@0: /** sl@0: Helper function to unregister and release surfaceId when CreateSurfaceL leave sl@0: @internalComponent sl@0: */ sl@0: void CVideoRenderer::Release() sl@0: { sl@0: UnregisterSurface(); sl@0: iSurfaceManager.CloseSurface(iSurfaceId); sl@0: } sl@0: sl@0: /** sl@0: Helper function to register surfaces for all displays sl@0: @internalComponent sl@0: */ sl@0: void CVideoRenderer::RegisterSurfaceL() sl@0: { sl@0: TInt screens = iWsSession.NumberOfScreens(); sl@0: TInt error; sl@0: sl@0: for(TInt i=0; iDestroySurface(&request); sl@0: User::WaitForRequest(logonRequest, request); sl@0: sl@0: if(logonRequest != KRequestPending) sl@0: { sl@0: // renderer thread got panic from surface update session, so panic client sl@0: TInt reason = iRendererThread.ExitReason(); sl@0: TExitCategoryName category = iRendererThread.ExitCategory(); sl@0: User::Panic(category,reason); sl@0: } sl@0: sl@0: // Thread is still alive and well sl@0: iRendererThread.LogonCancel(logonRequest); sl@0: User::WaitForRequest(logonRequest); // Consume the signal sl@0: sl@0: __ASSERT_DEBUG(request != KRequestPending, User::Panic(_L("CVR::DestroySurface"), KErrCorrupt)); sl@0: } sl@0: else sl@0: { sl@0: iRendererRelay->DestroySurface(NULL); sl@0: } sl@0: sl@0: UnregisterSurface(); sl@0: sl@0: delete iBufferManager; sl@0: iBufferManager = NULL; sl@0: iRendererRelay->SetBufferManager(NULL); sl@0: sl@0: TInt err = iSurfaceManager.CloseSurface(iSurfaceId); sl@0: DEBUGPRINT2(_L("RSurfaceManager::CloseSurface returned with %d"), err); sl@0: } sl@0: sl@0: /** sl@0: Retrieves the next free buffer from the renderer. The client can write data to sl@0: the buffer and update it to the screen using UpdateBuffer(). The buffer will sl@0: remain accessible to the client until it returns it to the renderer with sl@0: UpdateBuffer() or ReleaseBuffer(), the surface is destroyed (DestroySurface()), sl@0: or the renderer object is deleted. sl@0: sl@0: If no free buffers are available the client should wait for a sl@0: MvroVideoBufferAvailable() callback instead of polling for free buffers continuously. sl@0: sl@0: @return A pointer to the free buffer, or NULL if no free buffers are available. sl@0: */ sl@0: EXPORT_C TVideoFrameBuffer* CVideoRenderer::NextBuffer() sl@0: { sl@0: if (iBufferManager) sl@0: { sl@0: return iBufferManager->NextBuffer(); sl@0: } sl@0: sl@0: // surface had not been created, return null sl@0: return NULL; sl@0: } sl@0: sl@0: /** sl@0: Updates a surface buffer on the display. The buffer must have previously been sl@0: retrieved with NextBuffer() and must contain a valid video picture. The sl@0: renderer uses statistics from previous video frame rendering times to render sl@0: the picture as close to the requested presentation time as possible. sl@0: sl@0: @param aBuffer The buffer to update sl@0: @param aPresentationTime The system clock time when the buffer should be sl@0: visible on the display. If the time is zero the renderer will sl@0: update the buffer as soon as possible without further synchronization. sl@0: */ sl@0: EXPORT_C void CVideoRenderer::UpdateBuffer(TVideoFrameBuffer* aBuffer, const TTime& aPresentationTime) sl@0: { sl@0: __ASSERT_DEBUG(aBuffer != NULL, User::Panic(_L("CVR::UpdateBuffer"), KErrArgument)); sl@0: DEBUGPRINT3(_L("CVideoRenderer::UpdateBuffer entered with bufId=%d, aPresentationTime=%Ld"), sl@0: aBuffer->BufferId() , aPresentationTime.Int64()); sl@0: sl@0: // ignore request if a surface has not been created or has been destroyed sl@0: if (!iBufferManager) sl@0: { sl@0: return; sl@0: } sl@0: sl@0: if (iTimed) sl@0: { sl@0: TRequestStatus updateRequest = KRequestPending; sl@0: TRequestStatus logonRequest = KRequestPending; sl@0: sl@0: // While a function call is in progress this thread is suspended sl@0: // and the undertaker will not catch panics, listen for these here sl@0: iRendererThread.Logon(logonRequest); sl@0: sl@0: // Send request to renderer thread sl@0: iRendererRelay->UpdateBuffer(aBuffer, aPresentationTime, &updateRequest); sl@0: User::WaitForRequest(logonRequest, updateRequest); sl@0: sl@0: if(logonRequest != KRequestPending) sl@0: { sl@0: // renderer thread got panic from surface update session, so panic client sl@0: TInt reason = iRendererThread.ExitReason(); sl@0: TExitCategoryName category = iRendererThread.ExitCategory(); sl@0: User::Panic(category,reason); sl@0: } sl@0: sl@0: // Thread is still alive and well sl@0: iRendererThread.LogonCancel(logonRequest); sl@0: User::WaitForRequest(logonRequest); // Consume the signal sl@0: sl@0: __ASSERT_DEBUG(updateRequest != KRequestPending, User::Panic(_L("CVR::UpdateBuffer"), KErrCorrupt)); sl@0: } sl@0: else sl@0: { sl@0: iRendererRelay->UpdateBuffer(aBuffer, aPresentationTime, NULL); sl@0: } sl@0: } sl@0: sl@0: /** sl@0: Releases a buffer without updating it to the display. sl@0: The buffer must have previously been retrieved with NextBuffer() and sl@0: UpdateBuffer() is not called yet, otherwise the release request is ignored. sl@0: sl@0: @param aBuffer The buffer to release sl@0: */ sl@0: EXPORT_C void CVideoRenderer::ReleaseBuffer(TVideoFrameBuffer* aBuffer) sl@0: { sl@0: // ignore if if a surface has not been created or has beed destroyed sl@0: if (!iBufferManager || !aBuffer) sl@0: { sl@0: return; sl@0: } sl@0: sl@0: if (iBufferManager->ReleaseBuffer(aBuffer)) sl@0: { sl@0: // a buffer is releaseed, notify observer sl@0: iObserver.MvroVideoBufferAvailable(); sl@0: } sl@0: }