mmplugins/mmfwplugins/src/Plugin/Controller/Video/AviPlayController/srtdecoder/srtdecoder.cpp
changeset 0 b8ed18f6c07b
child 2 5c1df44f2eed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mmplugins/mmfwplugins/src/Plugin/Controller/Video/AviPlayController/srtdecoder/srtdecoder.cpp	Thu Oct 07 22:34:12 2010 +0100
@@ -0,0 +1,623 @@
+// 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:
+//
+
+#include <bitdev.h> 
+#include <fbs.h> 
+#include <gdi.h> 
+#include <utf.h> 
+
+#include "subtitlesource.h"
+#include "srtdecoder.h"
+#include "srtframe.h"
+
+// Define the separators for each subtitle frame in SRT file
+_LIT8(KSrtFrameSeparator1, "\r\n\r\n" );
+_LIT8(KSrtFrameSeparator2, "\n\n" );
+
+// The length of the separators
+const TInt KSrtFrameSeparator1Len = 4;
+const TInt KSrtFrameSeparator2Len = 2;
+
+// The margin size
+const TInt KSrtBmpMarginWidth = 6;
+const TInt KSrtBmpMarginHeight = 6;
+
+// The width of subtitle region is the same as the width of the video region, while the height is 33 
+// percent of the height of the video region. 
+const TInt KSrtSubtitleRegionYFactor = 33;
+
+// Define panic category
+_LIT(KSrtSubtitleDecoderPanicCategory, "SrtSubDec");
+
+// TSrtSubtitleDecoderPanics is an enumeration with the following entries:
+// EBadCall indicates a bad call
+enum TSrtSubtitleDecoderPanics
+    {
+    ESrtBadCall = 1,
+    ESrtUnexpected,
+    ESrtNotStarted,
+    ESrtAlreadyStarted
+    };
+
+
+/** Constructor with a reference to the subtitle source 
+    
+@param aSubtitleSource a reference to the subtitle source
+*/ 
+CSrtSubtitleDecoder::CSrtSubtitleDecoder(MSubtitleSource& aSubtitleSource) : 
+    iSource(aSubtitleSource)
+    {
+    }
+
+EXPORT_C CSrtSubtitleDecoder::~CSrtSubtitleDecoder()
+    {
+    delete iFrameParser;
+    iDecodeBuffer.Close();
+    }
+
+/** Allocates and constructs a srt subtitle decoder 
+    
+@param aSubtitleSource a reference to the subtitle source
+@return A pointer to the new object. 
+@leave If an error occurs, the method will leave with a system-wide error code. 
+*/    
+EXPORT_C CSrtSubtitleDecoder* CSrtSubtitleDecoder::NewL(MSubtitleSource& aSubtitleSource)
+    {
+    CSrtSubtitleDecoder* self = new(ELeave) CSrtSubtitleDecoder(aSubtitleSource);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    CleanupStack::Pop(self);
+    return self;
+    }
+
+/** Initialises the decoder
+    
+@leave If an error occurs, the method will leave with a system-wide error code.
+*/
+void CSrtSubtitleDecoder::ConstructL()
+    {
+    iFrameParser = CSrtFrame::NewL();
+    iDecodeBuffer.Create(KSubtitleDecoderBufferLength);
+    }
+    
+EXPORT_C TInt CSrtSubtitleDecoder::CalculateSubtitleRegion(const TRect& aScreenSize, TRect& aSubtitleRegion)
+    {
+    // TRect::IsEmpty is not used because the following test tests the minus values.
+    if ((aScreenSize.iBr.iX < 0) || (aScreenSize.iBr.iY < 0) || 
+        (aScreenSize.iTl.iX < 0) || (aScreenSize.iTl.iY < 0))
+        {
+        return KErrArgument;  
+        }
+    
+    if ((aScreenSize.iBr.iX <= aScreenSize.iTl.iX) || (aScreenSize.iBr.iY <= aScreenSize.iTl.iY))
+        {
+        return KErrArgument; 
+        }
+    
+    TPoint topleft;
+    
+    topleft.iX = aScreenSize.iTl.iX;
+    topleft.iY = aScreenSize.iTl.iY + aScreenSize.Height() * (100 - KSrtSubtitleRegionYFactor) / 100; 
+    
+    aSubtitleRegion.SetRect(topleft, aScreenSize.iBr);
+    
+    return KErrNone;
+    }
+
+/** Calculate the number of the characters in the first line. The full words are supposed to be displayed 
+unless the length of a word is bigger than one line space. 
+
+@param aFont The font in current use.
+@param aText The descriptor
+@param aWidthInPixels The available width for character display.
+@param aNumOfCharsToSkip The number of characters to skip, it can be 0, 1 (\n) or 2 (\r\n) 
+@return The number of characters which will be able to be displayed without exceeding the specified width. 
+The count starts from the beginning of the descriptor. 
+*/   
+TInt CSrtSubtitleDecoder::CalculateCharsInNextLine(const CFont &aFont, const TDesC &aText, TInt aWidthInPixels, TInt& aNumOfCharsToSkip)
+    {
+    const TChar KMMFSubtitleCharSpace(' ');
+    const TChar KMMFSubtitleCharCR('\r');
+    const TChar KMMFSubtitleCharLF('\n');
+    TInt charsInLine = aFont.TextCount(aText, aWidthInPixels);
+    aNumOfCharsToSkip = 0;
+        
+    if (charsInLine < aText.Length())
+        {
+        // For finding the last space of a descriptor which is not very big, using a simple loop 
+        // shall be more efficient than using TLex
+        for (TInt i = charsInLine; i > 0; i--)
+            {
+            if (KMMFSubtitleCharSpace == aText[i - 1])
+                {
+                charsInLine = i;
+                break;
+                }
+            }
+        }
+    
+    // Check \r\n or \n, the following loop is more efficient than TDesC::Find() in most cases 
+    // because both \r\n and \n are handled in one loop. 
+    for (TInt j = 0; j < charsInLine; j++)
+        {
+        if (KMMFSubtitleCharLF == aText[j])
+            {
+            if (j > 0)
+                {
+                // ignore \r if there is a \n
+                if (KMMFSubtitleCharCR == aText[j - 1])
+                    {
+                    charsInLine = j;
+                    aNumOfCharsToSkip = 2;
+                    }
+                else
+                    {
+                    charsInLine = j + 1;
+                    aNumOfCharsToSkip = 1;
+                    }
+                }
+            else
+                {
+                charsInLine = j + 1;
+                aNumOfCharsToSkip = 1;
+                }
+            break;
+            }
+        }
+
+    return charsInLine;
+    }
+
+/** Based on a descriptor containing a SRT subtitle frame, the function generates a single frame of 
+subtitle animation for the current time and outputs the region of the subtitle frame that has been 
+updated.
+
+@param aContent The subtitle frame as a descriptor
+@param aSubtitleFrame Reference to the current subtitle frame
+@param aDirtyRegion The decoder outputs the region of the subtitle frame that has been updated. 
+    i.e. the region that now contains new subtitle content.
+@leave If an error occurs, the method will leave with a system-wide error code. 
+*/
+void CSrtSubtitleDecoder::DecodeSubtitleContentL(const TDesC8& aContent, CFbsBitmap& aSubtitleFrame, TRect& aDirtyRegion)
+    {
+    TSize bmpSize = aSubtitleFrame.SizeInPixels(); 
+    TSize modifiedBmpSize;
+    
+    // the following value of "2" means two side of margin. 
+    modifiedBmpSize.iHeight = bmpSize.iHeight - KSrtBmpMarginHeight * 2;
+    modifiedBmpSize.iWidth = bmpSize.iWidth - KSrtBmpMarginWidth * 2;
+    
+    // test if the size of the bitmap is valid after taking margin into account   
+    if ((modifiedBmpSize.iHeight > 0) && (modifiedBmpSize.iWidth > 0))
+        {
+        // prepare and populate the bitmap
+        CFbsBitmapDevice* bmpDevice = CFbsBitmapDevice::NewL(&aSubtitleFrame);
+        CleanupStack::PushL(bmpDevice);
+        
+        CFbsBitGc* bitmapContext = CFbsBitGc::NewL(); //Create a GC to draw to the bitmap
+        bitmapContext->Activate(bmpDevice);
+        
+        CleanupStack::PushL(bitmapContext);
+        
+        bitmapContext->SetDrawMode(CGraphicsContext::EDrawModeWriteAlpha);
+        
+        // Use a white color (R=255, G=255, B=255) with alpha value of 0. 
+        TRgb transparentWhite(255, 255, 255, 0);
+        bitmapContext->SetBrushColor(transparentWhite);
+        
+        bitmapContext->Clear(); 
+        
+        bitmapContext->SetDrawMode(CGraphicsContext::EDrawModePEN);
+        
+        bitmapContext->SetPenStyle(CGraphicsContext::ESolidPen);
+        bitmapContext->SetPenColor(KRgbBlack);
+        
+        HBufC* content = CnvUtfConverter::ConvertToUnicodeFromUtf8L(aContent);
+        CleanupStack::PushL(content);
+        
+        // try to set up font
+        CFont *font = NULL;
+        TFontSpec fontSpec(KSrtTargetTypefaceName, KSrtTargetTypefaceHeightInTwips);
+        User::LeaveIfError(bmpDevice->GetNearestFontToDesignHeightInTwips(font, fontSpec));
+            
+        bitmapContext->UseFont(font);
+
+        TInt maxCharWidthInPixels = font->MaxCharWidthInPixels();
+        TInt heightInPixels = font->HeightInPixels();
+        TInt maxLines = modifiedBmpSize.iHeight / heightInPixels;    
+        TInt maxCharsPerLine = modifiedBmpSize.iWidth / maxCharWidthInPixels; 
+        TInt charsInLine = 0;
+        TInt currentRow = 0;
+        TPoint drawPosition;
+        drawPosition.iX = KSrtBmpMarginWidth;
+        drawPosition.iY = KSrtBmpMarginHeight + heightInPixels;
+        TInt unvisitedChars = content->Length();
+        TInt lineStartPos = 0;
+        TInt numOfCharsToSkip = 0;
+        
+        if ((maxLines > 0) && (maxCharsPerLine > 0))
+            {
+            do
+                {
+                TPtrC ptrUnvisitedDesc = content->Right(unvisitedChars);                
+                
+                charsInLine = CalculateCharsInNextLine(*font, ptrUnvisitedDesc, modifiedBmpSize.iWidth, numOfCharsToSkip); 
+                if (charsInLine > 0)
+                    {
+                    if (charsInLine > numOfCharsToSkip)
+                        {
+                        TPtrC ptrLine = content->Mid(lineStartPos, charsInLine - numOfCharsToSkip);
+                        // try to draw text line by line
+                        // Note that aPosition in DrawText indicates the bottom left of the first character. 
+                        bitmapContext->DrawText(ptrLine, drawPosition);
+                    
+                        drawPosition.iY += heightInPixels; 
+                        currentRow++;
+                        }
+                        
+                    lineStartPos += charsInLine;
+                    unvisitedChars -= charsInLine;
+                    }
+                }
+            while(charsInLine > 0 && currentRow < maxLines);
+            
+            // update the dirty region
+            aDirtyRegion.iTl.SetXY(KSrtBmpMarginWidth, KSrtBmpMarginHeight);
+
+            TInt actualMaxCharsPerLine = 0;
+            actualMaxCharsPerLine = Min(content->Length(), maxCharsPerLine);
+
+            aDirtyRegion.iBr.SetXY(
+                KSrtBmpMarginWidth + actualMaxCharsPerLine * maxCharWidthInPixels,
+                KSrtBmpMarginHeight + currentRow * heightInPixels + font->FontMaxDescent());
+            }
+        else
+            {
+            // bitmap too small for the font
+            User::Leave(KErrArgument);
+            }
+
+        bmpDevice->ReleaseFont(font);
+        bitmapContext->DiscardFont();
+        
+        CleanupStack::PopAndDestroy(3, bmpDevice); // content, bitmapContext, bmpDevice
+        }
+    else
+        {
+        // bitmap too small or the length of content is 0
+        User::Leave(KErrArgument);
+        }
+    }
+    
+/** Try to get the next valid frame 
+
+@param aSearchByVideoPosition Indicate if search for the next valid frame matching aVideoPosition
+@param aVideoPosition The video position in microsecond
+@leave If an error occurs, the method will leave with a system-wide error code. 
+*/
+void CSrtSubtitleDecoder::GetNextValidFrameL(TBool aSearchByVideoPosition, TInt64 aVideoPosition)
+    {
+    TInt decoderBufferMaxLen = iDecodeBuffer.MaxLength();
+    TInt errorFrame = KErrNone;
+    TInt error = KErrNone;
+    TInt sepLen = 0;
+    TInt i = 0;
+    TChar currentChar = 0;
+    TBool frameFound = EFalse;
+    
+    if (iLastFrame)
+    	{
+    	User::Leave(KErrEof);
+    	}
+    
+    while (!frameFound)
+        {
+        // if a frame is not available in iDecodeBuffer, fill iBuffer until CRLFCRLF is available in iBuffer
+        TPtrC8 restOfData = iDecodeBuffer.Right(iDecodeBuffer.Length() - iDecodeBufferPosition);
+        TInt sepPosition = 0;
+        
+        // try to extract a frame from iDecodeBuffer
+        errorFrame = GetFrameSeparatorInfo(restOfData, sepLen, sepPosition);
+        if (KErrNotFound != errorFrame)
+            {
+            User::LeaveIfError(errorFrame);
+            }
+            
+        // if no frame can be extracted, read source until a frame is available or get an EOF error.
+        while (KErrNotFound == errorFrame)
+            {
+            // move the unvisited data in decode buffer to the head before read from the source
+            if (iDecodeBufferPosition != 0)
+                {
+                // move the unvisited data in decode buffer to the head
+                if (iDecodeBufferPosition <= iDecodeBuffer.Length() - 1)
+                    {
+                    TPtrC8 newSubtitleFrame = iDecodeBuffer.Right(iDecodeBuffer.Length() - (iDecodeBufferPosition));
+                    
+                    // use iBuffer to hold the preserved data when rotating data in iDecodeBuffer
+                    if (iBuffer.MaxLength() < newSubtitleFrame.Length())
+                        {
+                        // Some data in iBuffer make iDecodeBuffer hold at least one frame, and so
+                        // iBuffer should always be larger than the preserved data in iDecodeBuffer
+                        User::Panic(KSrtSubtitleDecoderPanicCategory, ESrtUnexpected);
+                        }
+                        
+                    iBuffer.Copy(newSubtitleFrame);
+                    iDecodeBuffer.Copy(iBuffer);
+                    }
+
+                iDecodeBufferPosition = 0;
+                }
+                
+            error = iSource.GetBuffer(iBuffer);
+            if ((KErrNone != error) && (KErrEof != error))
+                {
+                User::Leave(error);
+                }
+            
+            if (iBuffer.Length() > 0)
+                {
+                TInt bufLen = iDecodeBuffer.Length();
+                TInt newBufLen = iBuffer.Length();
+                
+                if (decoderBufferMaxLen < newBufLen + bufLen)
+                    {
+                    iDecodeBuffer.ReAllocL(newBufLen + bufLen);
+                    decoderBufferMaxLen = iDecodeBuffer.MaxLength();
+                    }
+                iDecodeBuffer.Append(iBuffer);
+                }
+                
+            if (KErrEof == error)
+                {
+                // reach the end of the subtitle source but cannot find new line separators.  Frame 
+                // ends at EOF marker then.  No more frames can be read after this one.
+                sepLen = 0;
+                sepPosition = iDecodeBuffer.Length();
+                errorFrame = KErrNone;
+                if (iLastFrame)
+                	{
+                	User::Leave(KErrEof);
+                	}
+                iLastFrame = ETrue;
+                }
+            else
+            	{
+            	errorFrame = GetFrameSeparatorInfo(iDecodeBuffer, sepLen, sepPosition);
+            	}
+            
+            if (KErrNotFound != errorFrame)
+                {
+                User::LeaveIfError(errorFrame);
+                }
+            }
+        
+        // extract a possible frame
+        TPtrC8 subtitleFrameDescriptor = iDecodeBuffer.Mid(iDecodeBufferPosition, sepPosition);
+        
+        // Find the start of the next frame, and update the current position of the decode buffer by 
+        // the following method: from the start of iDecodeBuffer, find the position of the first digit 
+        // character after the first frame separator. 
+        // Not the current position of the decode buffer is updated before parsing the frame, so the 
+        // potential invalid frame will be skipped.  
+        iDecodeBufferPosition += sepPosition + sepLen;
+        TInt firstDigitPosFromCurrentPos = KErrNotFound;
+        for (i = iDecodeBufferPosition; i < iDecodeBuffer.Length(); i++)
+            {
+            currentChar = iDecodeBuffer[i];
+            if (currentChar.IsDigit())
+                {
+                firstDigitPosFromCurrentPos = i;
+                break;
+                }
+            }
+        if (KErrNotFound == firstDigitPosFromCurrentPos) 
+            {
+            iDecodeBufferPosition = iDecodeBuffer.Length() - 1;
+            }
+        else
+            {
+            iDecodeBufferPosition = firstDigitPosFromCurrentPos;
+            }
+        
+        // parse the frame 
+        TRAPD(parseError, iFrameParser->ParseL(subtitleFrameDescriptor, aSearchByVideoPosition, aVideoPosition));
+        if (KErrNone == parseError)
+            {
+            frameFound = ETrue;
+            if (!aSearchByVideoPosition)
+                {
+                // skip the past frames, such as 
+                // 2\r\n00:00:02,000 --> 00:00:04,000\r\nhome\r\n\r\n
+                // 1\r\n00:00:00,000 --> 00:00:02,000\r\nwelcome\r\n\r\n
+                // 3\r\n00:00:04,000 --> 00:00:06,000\r\nagain
+                if (iFrameParser->EndTime() < iEndTimeOfLastValidFrame)
+                    {
+                    frameFound = EFalse;
+                    }  
+                else
+                    {
+                    iEndTimeOfLastValidFrame = iFrameParser->EndTime();
+                    }  
+                }      
+            else
+                {
+                iEndTimeOfLastValidFrame = iFrameParser->EndTime();
+                }    
+            }
+        else
+            {
+            if (KErrArgument != parseError)
+                {
+                User::Leave(parseError);
+                }
+            }
+        }
+    }
+
+/** The decoder generates a single frame of subtitle animation for the current time.   
+The decoder maintains track of the video position. The video position can be updated 
+via a call to MSubtitleDecoder::SetVideoPosition(). 
+
+@param aSubtitleFrame Reference to the current subtitle frame
+@param aDirtyRegion The decoder outputs the region of the subtitle frame that has been updated. 
+    i.e. the region that now contains new subtitle content.
+@param aDisplayTime The time in microseconds from the current stream position that the subtitle is due. 
+    DevSubtitle will draw aSubtitleFrame after aDisplayTime has elapsed.
+@param aDisplayDuration The time in microseconds that this frame should be displayed for. The CRP will clear 
+    this frame after aDisplayFor microseconds
+@leave If an error occurs, the method will leave with a system-wide error code.
+*/   
+EXPORT_C void CSrtSubtitleDecoder::GetNextFrameL(CFbsBitmap& aSubtitleFrame, TRect& aDirtyRegion, TTimeIntervalMicroSeconds& aDisplayTime, TTimeIntervalMicroSeconds& aDisplayDuration)
+    {
+    if (!iStarted)
+        {
+        User::Panic(KSrtSubtitleDecoderPanicCategory, ESrtNotStarted);
+        }
+        
+    TTime currentTime;
+    currentTime.UniversalTime();
+    TTimeIntervalMicroSeconds elapsedTime = currentTime.MicroSecondsFrom(iStartSystemTime);
+    TInt64 currentVideoPositionValue = elapsedTime.Int64() + iStartVideoPosition;
+    
+    if (iFirstSearch)
+        {
+        // skip the past frame
+        GetNextValidFrameL(ETrue, currentVideoPositionValue);
+            
+        iFirstSearch = EFalse;
+        }
+    else
+        {
+        // get the next frame
+        GetNextValidFrameL(EFalse, 0);
+        }
+    
+    TInt64 elapsedDuration = 0;
+    // if start time <= current video time, this frame needs to be started immediately.    
+    if (iFrameParser->StartTime() <= currentVideoPositionValue)
+        {
+        aDisplayTime = 0;
+        elapsedDuration = currentVideoPositionValue - iFrameParser->StartTime();
+        }
+    else
+        {
+        aDisplayTime = iFrameParser->StartTime() - currentVideoPositionValue;
+        }
+    
+    // if end time < current video time, this duration becomes 0.
+    if (iFrameParser->EndTime() <= currentVideoPositionValue)
+        {
+        aDisplayDuration = 0;
+        }
+    else
+        {
+        aDisplayDuration = iFrameParser->Duration() - elapsedDuration;
+        }
+            
+    // decode the content into bitmap and get dirty region
+    TPtrC8 ptrContent = iFrameParser->Content();
+    DecodeSubtitleContentL(ptrContent, aSubtitleFrame, aDirtyRegion);
+    }
+    
+EXPORT_C TLanguage CSrtSubtitleDecoder::SubtitleLanguageL()
+    {
+    return iSource.SubtitleLanguageL();
+    }
+    
+EXPORT_C void CSrtSubtitleDecoder::GetSupportedSubtitleLanguagesL(RArray<TLanguage>& aLanguages)
+    {
+    iSource.GetSupportedSubtitleLanguagesL(aLanguages);
+    }
+    
+EXPORT_C void CSrtSubtitleDecoder::SetSubtitleLanguageL(TLanguage aLanguage)
+    {
+    iSource.SetSubtitleLanguageL(aLanguage);
+    }
+    
+EXPORT_C void CSrtSubtitleDecoder::SetVideoPosition(const TTimeIntervalMicroSeconds& aPosition)
+    {
+    if (aPosition < 0)
+        {
+        User::Panic(KSrtSubtitleDecoderPanicCategory, ESrtBadCall);
+        }
+        
+    iStartVideoPosition = aPosition.Int64();
+    iLastFrame = EFalse;
+    }
+    
+EXPORT_C void CSrtSubtitleDecoder::Start()
+    {
+    if (iStarted)
+        {
+        User::Panic(KSrtSubtitleDecoderPanicCategory, ESrtAlreadyStarted);
+        }
+    iSource.SetPosition(0);
+    iStartSystemTime.UniversalTime();
+    iStarted = ETrue;
+    iDecodeBufferPosition = 0;
+    iFirstSearch = ETrue;
+    iEndTimeOfLastValidFrame = 0;
+    iLastFrame = EFalse;
+    }
+    
+EXPORT_C void CSrtSubtitleDecoder::Stop()
+    {
+    iSource.SetPosition(0);
+    iDecodeBuffer.SetLength(0);
+    iBuffer.SetLength(0);
+    iStartVideoPosition = 0; 
+    iStartSystemTime = 0;
+    iStarted = EFalse;
+    iDecodeBufferPosition = 0;
+    iLastFrame = EFalse;
+    }
+    
+/** Searches for the first occurrence of the SRT frame separator (\r\n\r\n or \n\n) within this 
+    descriptor and return the length and the position of the separator.
+        
+@param aDes The buffer being searched
+@param aSeparatorLength Output the length of the SRT frame separator
+@param aPosition The offset of the SRT frame separator from the beginning of this descriptor's data.
+@return A system-wide error code, KErrNotFound if the SRT frame separator cannot be found. 
+*/
+TInt CSrtSubtitleDecoder::GetFrameSeparatorInfo(const TDesC8& aDes, TInt& aSeparatorLength, TInt& aPosition)
+    {
+    TInt errCode = KErrNone;
+    
+    aPosition = aDes.Find(KSrtFrameSeparator1);
+    if (KErrNotFound == aPosition)
+        {
+        aPosition = aDes.Find(KSrtFrameSeparator2);
+        if (KErrNotFound == aPosition)
+            {
+            errCode = KErrNotFound;
+            }
+        else
+            {
+            aSeparatorLength = KSrtFrameSeparator2Len;
+            }
+        }
+    else
+        {
+        aSeparatorLength = KSrtFrameSeparator1Len;
+        }   
+    
+    return errCode;
+    }
+
+
+
+