[datatype-dev] CR: Malformed FLV file can return invalid length
Mac McLain mmclain at real.comModified by: mmclain at real.com Date: 06:24:09 Project: RealPlayer 12 Synopsis: The code to determine the duration of an FLV file can produce inaccurate results when presented with certain malformed, but playable FLV files. In the case where walking backwards from the last block in an FLV file to find the last valid duration fails due to invalid offsets, continue walking forward from the last successfully read block until the last block is read. Files Added: none Files Modified: datatype/flash/flv/fileformat/flv_file_format.cpp datatype/flash/flv/fileformat/pub/flv_file_format.h Image Size and Heap Use impact (Client-Only): none Platforms and Profiles Affected: Platform: win32-i386-vc7 Platforms and Profiles Build Verified: Platform: win32-i386-vc7 Profile: helix-client-all-defines target(s): playinst Branch: hxclient_3_1_0_atlas, HEAD Index: pub/flv_file_format.h =================================================================== RCS file: /cvsroot/datatype/flash/flv/fileformat/pub/flv_file_format.h,v retrieving revision 1.7.2.9 diff -u -1 -0 -r1.7.2.9 flv_file_format.h --- pub/flv_file_format.h 20 May 2009 13:46:04 -0000 1.7.2.9 +++ pub/flv_file_format.h 24 Jun 2009 16:18:29 -0000 @@ -199,20 +199,22 @@ kStateGFHScanDurationReadDonePending, kStateFileHeaderSent, kStateStreamHeadersSent, kStateSendingPackets, kStateGPReadDonePending, kStateGPTagReadDonePending, kStateGPSeekDonePending, kStateSeekFinalSeekDonePending, kStateSeekSearchSeekDonePending, kStateSeekSearchReadDonePending, + kStateForwardDurationSeekDonePending, + kStateForwardDurationReadDonePending, kNumStates }; INT32 m_lRefCount; IUnknown* m_pContext; IHXRequest* m_pRequest; IHXFormatResponse* m_pFormatResponse; IHXFileObject* m_pFileObject; IHXFileStat* m_pFileStat; IHXValues* m_pRequestedMetaInfo; @@ -221,20 +223,21 @@ CStreamMergeSorter* m_pMergeSorter; CHXSimpleList* m_pAVCRTPTimeCorrectionQueue; IHXBuffer* m_pFLVHeader; IHXBuffer* m_pFLVTagHeader; IHXBuffer* m_pAudioSpecificConfig; IHXBuffer* m_pAVCDecoderConfigurationRecord; UINT32 m_ulState; UINT32 m_ulSeekOffsetRequested; UINT32 m_ulReadBytesRequested; UINT32 m_ulFileOffset; + UINT32 m_ulLastForwardReadFileOffset; UINT32 m_ulFileSize; UINT32 m_ulStreamCount; UINT32 m_ulNumStreamHeadersSent; UINT32 m_ulSeekTime; UINT32 m_ulWidth; UINT32 m_ulHeight; UINT32 m_ulDuration; UINT32 m_ulProcessTimeUnits; UINT32 m_ulFirstAudioTimeStamp; UINT32 m_ulLastAudioTimeStamp; Index: flv_file_format.cpp =================================================================== RCS file: /cvsroot/datatype/flash/flv/fileformat/flv_file_format.cpp,v retrieving revision 1.9.2.21 diff -u -1 -0 -r1.9.2.21 flv_file_format.cpp --- flv_file_format.cpp 26 May 2009 01:39:58 -0000 1.9.2.21 +++ flv_file_format.cpp 24 Jun 2009 16:18:29 -0000 @@ -144,20 +144,21 @@ , m_ulFirstVideoTimeStamp(0) , m_ulNumVideoFrames(0) , m_ulMergeSorterMaxTimespanPref(0) , m_ulMaxTimeBetweenKeyFramesFromMetaData(0) , m_ulNumBytesInNALUnitLength(0) , m_dVideoFrameRate(0.0) , m_ulState(kStateConstructed) , m_ulSeekOffsetRequested(0) , m_ulReadBytesRequested(0) , m_ulFileOffset(0) + , m_ulLastForwardReadFileOffset(0) , m_ulFileSize(0) , m_usAudioStreamNum(INVALID_STREAM_NUMBER) , m_usVideoStreamNum(INVALID_STREAM_NUMBER) , m_pLastVideoPacket(NULL) , m_bReadMetaPacket(FALSE) , m_bAudioOnlyFile(FALSE) , m_bVideoOnlyFile(FALSE) , m_bHaveFileSize(FALSE) , m_bHaveWidth(FALSE) , m_bHaveHeight(FALSE) @@ -1190,20 +1191,22 @@ // and video timestamps. if (!bLinearFileSystem && m_bHaveFileSize) { // The filesystem is capable of random access // and we don't want to trust the meta-data duration. // // The last 4 bytes of the FLV file should have a // previous tag size in it. That will allow us to // move backward through the file. // + // Save off our current file offset in case we need to revisit it to read forward + m_ulLastForwardReadFileOffset = m_ulFileOffset; // Set the state m_ulState = kStateGFHScanDurationFirstSeekDonePending; // Compute the file offset to seek to m_ulSeekOffsetRequested = m_ulFileSize - HX_FLV_PREV_TAG_SIZE; // Seek to 4 bytes from the end of the file m_pFileObject->Seek(m_ulSeekOffsetRequested, FALSE); } else { // Set the state @@ -2178,20 +2181,29 @@ } // Do we need to continue the reverse scan? if (bContinueReverseScan) { // We need to continue reverse scanning the tags // // Set the state m_ulState = kStateGFHScanDurationSeekDonePending; // Set the requested seek offset m_ulSeekOffsetRequested = m_ulFileOffset - m_cTagHeader.GetPreviousTagSize() - 19; + + if (m_ulSeekOffsetRequested >= m_ulFileSize || m_ulSeekOffsetRequested > m_ulFileOffset) + { + // + // The FLV must be corrupted, so read forward from where we last read to see if we can't build it that way + // + m_ulSeekOffsetRequested = m_ulLastForwardReadFileOffset; + m_ulState = kStateForwardDurationSeekDonePending; + } // Seek to the previous tag m_pFileObject->Seek(m_ulSeekOffsetRequested, FALSE); } else { // We're done scanning, so we can seek back to the first tag // // Set the state m_ulState = kStateGFHFinalSeekDonePending; // Set the requested seek offset @@ -2208,22 +2220,75 @@ // // Set the state m_ulState = kStateGFHFinalSeekDonePending; // Set the requested seek offset m_ulSeekOffsetRequested = m_cHeader.GetDataOffset(); // Seek to the data offset m_pFileObject->Seek(m_ulSeekOffsetRequested, FALSE); } // Clear the return value retVal = HXR_OK; + } + else if (m_ulState == kStateForwardDurationReadDonePending) + { + if (SUCCEEDED(status)) + { + if (pBuffer->GetSize() >= HX_FLV_TAG_HEADER_SIZE) + { + // Parse the tag header + BOOL bContinueSeek = true; + BYTE* pBuf = pBuffer->GetBuffer(); + UINT32 ulLen = pBuffer->GetSize(); + retVal = m_cTagHeader.Parse(&pBuf, &ulLen); + if (SUCCEEDED(retVal)) + { + switch (m_cTagHeader.GetTagType()) + { + case HX_FLV_TAG_TYPE_AUDIO: + // Save the last audio timestamp + m_ulLastAudioTimeStamp = m_cTagHeader.GetTimeStamp(); + m_bHaveLastAudioTimeStamp = TRUE; + break; + + case HX_FLV_TAG_TYPE_VIDEO: + break; + + default: + bContinueSeek = false; + break; + }; + + if (bContinueSeek) + { + m_ulSeekOffsetRequested = m_ulFileOffset + m_cTagHeader.GetDataSize() - 1; + m_ulState = kStateForwardDurationSeekDonePending; + // Seek to the next tag + m_pFileObject->Seek(m_ulSeekOffsetRequested, FALSE); + } + else + { + // Set the state + m_ulState = kStateGFHFinalSeekDonePending; + // Set the requested seek offset + m_ulSeekOffsetRequested = m_cHeader.GetDataOffset(); + // Seek to the data offset + m_pFileObject->Seek(m_ulSeekOffsetRequested, FALSE); + } + + } + } + } + // Clear the return value + retVal = HXR_OK; } + return retVal; } STDMETHODIMP CHXFLVFileFormat::WriteDone(HX_RESULT status) { return HXR_NOTIMPL; } STDMETHODIMP CHXFLVFileFormat::SeekDone(HX_RESULT status) { @@ -2434,20 +2499,32 @@ // Set the state m_ulState = kStateGFHFinalSeekDonePending; // Set the requested seek offset m_ulSeekOffsetRequested = m_cHeader.GetDataOffset(); // Seek to the data offset m_pFileObject->Seek(m_ulSeekOffsetRequested, FALSE); } // Clear the return value retVal = HXR_OK; } + else if (m_ulState == kStateForwardDurationSeekDonePending) + { + if (SUCCEEDED(status)) + { + // Set the state + m_ulState = kStateForwardDurationReadDonePending; + // Save the number of read bytes requested + m_ulReadBytesRequested = HX_FLV_TAG_HEADER_SIZE + 1; + // Read HX_FLV_TAG_HEADER_SIZE plus one bytes + m_pFileObject->Read(HX_FLV_TAG_HEADER_SIZE + 1); + } + } return retVal; } STDMETHODIMP CHXFLVFileFormat::StatDone(HX_RESULT status, UINT32 ulSize, UINT32 ulCreationTime, UINT32 ulAccessTime, UINT32 ulModificationTime, UINT32 ulMode) { HX_RESULT retVal = HXR_UNEXPECTED; if (m_ulState == kStateIFFStatDonePending) @@ -2513,20 +2590,31 @@ { // Set the height property rpHdr->SetPropertyULONG32("Height", m_ulHeight); rpHdr->SetPropertyULONG32("FrameHeight", m_ulHeight); } // Set the duration property. If we have a video-specific duration, // then use that. If not, then if we have a file-level duration, then // use that. If we don't have either a video-specific duration or a // file-level duration, then set some default duration and assert. + if (!m_bHaveVideoDuration && !m_bHaveDuration) + { + // + // Use the last audio duration if we have that. + // + if (m_bHaveLastAudioTimeStamp) + { + m_ulDuration = m_ulLastAudioTimeStamp; + m_bHaveDuration = true; + } + } HX_ASSERT(m_bHaveVideoDuration || m_bHaveDuration); UINT32 ulDuration = (m_bHaveVideoDuration ? m_ulVideoDuration : (m_bHaveDuration ? m_ulDuration : 10000)); retVal = rpHdr->SetPropertyULONG32("Duration", ulDuration); // Do we have the video bitrate? if (!m_bHaveVideoBitrate) { // We don't know the video bitrate, but we may be // able to estimate it. If: a) we know the duration -------------- next part -------------- An HTML attachment was scrubbed... URL: http://lists.helixcommunity.org/pipermail/datatype-dev/attachments/20090624/00b170ec/attachment-0001.html