//==========================================================================; // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR // PURPOSE. // // Copyright (c) 1992 - 1998 Microsoft Corporation. All Rights Reserved. // //--------------------------------------------------------------------------; // /******************************Module*Header*******************************\ * Module Name: MpgVideo.cpp * * Implements a prototype Mpeg Video Software codec. It just consumes * the passed in packets. * \**************************************************************************/ #include "MpgVideo.h" // define the GUIDs for streams and my CLSID in this file #include #include "mpegUids.h" #include "palette.h" // setup data const AMOVIESETUP_MEDIATYPE psudIpPinTypes[] = { { &MEDIATYPE_Video // clsMajorType , &MEDIASUBTYPE_MPEG1Packet } // clsMinorType , { &MEDIATYPE_Video // clsMajorType , &MEDIASUBTYPE_MPEG1Payload } }; // clsMinorType const AMOVIESETUP_MEDIATYPE sudOpPinTypes = { &MEDIATYPE_Video // clsMajorType , &MEDIASUBTYPE_NULL }; // clsMinorType const AMOVIESETUP_PIN psudPins[] = { { L"Input" // strName , FALSE // bRendered , FALSE // bOutput , FALSE // bZero , FALSE // bMany , &CLSID_NULL // clsConnectsToFilter , L"Output" // strConnectsToPin , 2 // nTypes , psudIpPinTypes } // lpTypes , { L"Output" // strName , FALSE // bRendered , TRUE // bOutput , FALSE // bZero , FALSE // bMany , &CLSID_NULL // clsConnectsToFilter , L"Input" // strConnectsToPin , 1 // nTypes , &sudOpPinTypes } }; // lpTypes const AMOVIESETUP_FILTER sudMPEGVideo = { &CLSID_CMpegFrameworkVideoCodec // clsID , L"MPEG Framework Audio Codec" // strName , 0x00600000 // dwMerit , 2 // nPins , psudPins }; // lpPin /* ------------------------------------------------------------------------- ** list of class ids and creator functions for class factory ** ------------------------------------------------------------------------- */ CFactoryTemplate g_Templates[] = { {L"MPEG Framework Video Codec" , &CLSID_CMpegFrameworkVideoCodec , CMpegVideoCodec::CreateInstance , NULL , &sudMPEGVideo } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); const TCHAR chRegistryKey[] = TEXT("Software\\Microsoft\\Multimedia\\ActiveMovie Filters\\Sample MPEG Video Decoder"); /******************************Public*Routine******************************\ * GetDecoderInt * * * * History: * * \**************************************************************************/ int GetDecoderInt( const TCHAR *pKey, int iDefault ) { HKEY hKey; LONG lRet; int iRet = iDefault; lRet = RegOpenKey(HKEY_CURRENT_USER, chRegistryKey, &hKey); if (lRet == ERROR_SUCCESS) { DWORD dwType, dwLen; dwLen = sizeof(iRet); if (ERROR_SUCCESS != RegQueryValueEx(hKey, pKey, 0L, &dwType, (LPBYTE)&iRet, &dwLen)) { iRet = iDefault; } RegCloseKey(hKey); } return iRet; } /* ------------------------------------------------------------------------- ** CMpegVideoCodec ** ------------------------------------------------------------------------- */ CMpegVideoCodec::CMpegVideoCodec( TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr ) : CTransformFilter(pName, pUnk, CLSID_CMpegVideoCodec), m_pVideoDecoder(NULL), m_PtsQueue(NAME("Pts queue"), 30, FALSE, FALSE), m_pFrameBuff(NULL), m_Buffer(NULL), m_pOutSample(NULL) { // // Pick up any user preferences - this should get moved to the register // m_IgnoreQualityMessage = GetDecoderInt(TEXT("IgnoreQMessages"), FALSE); m_dwCtrlDefault = GetDecoderInt(TEXT("VideoFramesDecoded"), DECODE_IPB); m_dwCtrlDefault &= 0x3F; m_dwQualDefault = GetDecoderInt(TEXT("VideoQuality"), 0); m_dwQualDefault &= 0x30000000; m_dwCtrl = (m_dwCtrlDefault | m_dwQualDefault); if (GetDecoderInt(TEXT("GreyScale"), 0)) { m_PaletteType = GREY_PALETTE; m_dwOutputFormatDib = MM_Y_DIB; m_dwOutputFormatDdb = MM_Y_DDB; } else { m_PaletteType = COLOUR_PALETTE; m_dwOutputFormatDib = MM_RGB8_DIB; m_dwOutputFormatDdb = MM_RGB8_DDB; } // // Reset frame stats // ZeroMemory(m_dwFramesSkipped, sizeof(m_dwFramesSkipped)); ZeroMemory(m_dwFramesDecoded, sizeof(m_dwFramesDecoded)); m_PerfDecode = MSR_REGISTER(TEXT("Decode Time - Start/Stop")); m_QualMsg = MSR_REGISTER(TEXT("Quality Message")); m_FrameDrawn = MSR_REGISTER(TEXT("Frame Drawn")); m_FrameType = MSR_REGISTER(TEXT("Frame Type")); } CMpegVideoCodec::~CMpegVideoCodec( ) { CTimePosition *pTimePos; DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::~CMpegVideoCodec"))); delete m_pVideoDecoder; m_pVideoDecoder = NULL; delete [] m_pFrameBuff; m_pFrameBuff = NULL; delete [] m_Buffer; m_Buffer = NULL; // // Purge the PTS queue. // while( (pTimePos = m_PtsQueue.RemoveHead()) != NULL) { delete pTimePos; } // // This should have been deleted in BreakConnect // ASSERT(m_Buffer == NULL); } /******************************Public*Routine******************************\ * CreateInstance * * This goes in the factory template table to create new instances * \**************************************************************************/ CUnknown * CMpegVideoCodec::CreateInstance( LPUNKNOWN pUnk, HRESULT * phr ) { DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::CreateInstance"))); return new CMpegVideoCodec(TEXT("MPEG Video codec filter"), pUnk, phr); } /******************************Public*Routine******************************\ * NonDelegatingQueryInterface * * Here we would reveal ISpecifyPropertyPages and IMpegVideoDecoder if * the framework had a property page. * \**************************************************************************/ STDMETHODIMP CMpegVideoCodec::NonDelegatingQueryInterface( REFIID riid, void ** ppv ) { return CTransformFilter::NonDelegatingQueryInterface(riid, ppv); } /******************************Public*Routine******************************\ * EndOfStream * * * \**************************************************************************/ HRESULT CMpegVideoCodec::EndOfStream() { DbgLog((LOG_TRACE, 2, TEXT("End of stream called"))); CAutoLock lck(&m_csReceive); if (m_pVideoDecoder == NULL) { return VFW_E_WRONG_STATE; } DecodeUntilBufferEmpty(); ResetVideoDecoder(); return CTransformFilter::EndOfStream(); } /******************************Public*Routine******************************\ * EndFlush * * * \**************************************************************************/ HRESULT CMpegVideoCodec::EndFlush() { DbgLog((LOG_TRACE, 2, TEXT("End flush called"))); CAutoLock lck(&m_csReceive); ResetVideoDecoder(); return CTransformFilter::EndFlush(); } /******************************Public*Routine******************************\ * Receive * * Copy the input sample into our buffer. Loop while the buffer size is * greater than or equal to the size given in the Vbv for the next picture * decode the picture and pass it along to the output pin for rendering. * \**************************************************************************/ HRESULT CMpegVideoCodec::Receive( IMediaSample *pSample ) { // // Make sure the pin doesn't go inactive on us // HRESULT hr; CAutoLock lck(&m_csReceive); if (m_pVideoDecoder == NULL || m_pFrameBuff == NULL) { return E_UNEXPECTED; } // // Check for a discontinuity, if one is found decode and display any // frames left in the circular buffer, reset the decoder and then continue // processing the current sample. // if (pSample->IsDiscontinuity() == S_OK) { DecodeUntilBufferEmpty(); ResetVideoDecoder(); // // Find out what the current stop time is... // ... get logical duration from upstream // HRESULT hrPos; REFTIME dStart, dStop; IMediaPosition *pPosition = NULL; hrPos = m_pInput->GetConnected()->QueryInterface(IID_IMediaPosition, (void **)&pPosition); if ( SUCCEEDED(hrPos) && pPosition != NULL && SUCCEEDED(pPosition->get_CurrentPosition(&dStart)) && SUCCEEDED(pPosition->get_StopTime(&dStop))) { m_tStop = (CRefTime)(COARefTime)dStop - (CRefTime)(COARefTime)dStart; } else { m_tStop = 0x7FFFFFFFFFFFFFFF; } if (pPosition != NULL) { pPosition->Release(); } DbgLog((LOG_TRACE, 2, TEXT("Receive() : Discontinuity - setting stop time to %s"), (LPCTSTR)CDisp(m_tStop))); } m_pSample = NULL; m_LenLeftInPacket = 0L; // // If this sample is a sync point we need to // update our clock and reset the count of // samples received since the last sync point. // if (pSample->IsSyncPoint() == S_OK) { CRefTime tStart, tStop; CTimePosition *pTimePos; pSample->GetTime((REFERENCE_TIME*)&tStart, (REFERENCE_TIME*)&tStop); pTimePos = new CTimePosition(&tStart, m_BufferCurr + m_BufferFullness >= m_BufferStart + m_VBlockSize ? m_BufferCurr + m_BufferFullness - m_VBlockSize : m_BufferCurr + m_BufferFullness); DbgLog(( LOG_TRACE, 4, TEXT("PTS: %s Buffer pos: %ld"), (LPCTSTR)CDisp(tStart), m_BufferCurr + m_BufferFullness )); m_PtsQueue.AddTail(pTimePos); } // // Decode and if necessary display as many frames as // possible until we exhaust our circular buffer. // do { hr = CopySampleToBuffer(pSample); while (SUCCEEDED(hr) && (m_BufferFullness >= m_seqInfo.lvbv)) { hr = DecodeNextPicture(); } } while (S_OK == hr && (m_LenLeftInPacket != 0L)); return hr; } /*****************************Private*Routine******************************\ * ResetVideoDecoder * * * \**************************************************************************/ void CMpegVideoCodec::ResetVideoDecoder() { // // Make him eat our sequence header // // m_BufferStart = m_BufferCurr = m_Buffer->GetPointer(); m_BufferStart = m_BufferCurr = m_Buffer; m_BufferFullness = m_seqInfo.lActualHeaderLen; CopyMemory(m_BufferStart, m_seqInfo.RawHeader, m_seqInfo.lActualHeaderLen); m_TimeSinceLastSyncPoint = 0L; m_pOutSample = NULL; // // Don't play anything until we get a sync point // but don't make it too negative or we'll overflow! // m_TimeAtLastSyncPoint = (LONGLONG)-0x7FFFFFFFFFFFFF; m_tStopPrev = m_TimeAtLastSyncPoint; m_pVideoDecoder->ResetVideo(); // // Purge the PTS queue. // CTimePosition *pTimePos; while( (pTimePos = m_PtsQueue.RemoveHead()) != NULL) { delete pTimePos; } // // Reset IP tracking // m_NextIP.Reset(); } /*****************************Private*Routine******************************\ * DecodeUntilBufferEmpty * * * \**************************************************************************/ void CMpegVideoCodec::DecodeUntilBufferEmpty() { HRESULT hr = S_OK; while (SUCCEEDED(hr) && (m_BufferFullness > 0)) { LPBYTE pCurrentCurr = m_BufferCurr; DbgLog((LOG_TRACE, 3, TEXT("Forcing out a frame"))); hr = DecodeNextPicture(); if (S_OK != hr || m_BufferCurr == pCurrentCurr) { // // We're not making any progress! // DbgLog((LOG_ERROR, 2, TEXT("CMpegVideoCodec::DecodeUntilBufferEmpty() ") TEXT("- Stuck with %d bytes in buffer"), m_BufferFullness)); break; } } // // Now see if there's a frame decoded but not presented // if (m_NextIP.TimeToDraw()) { DbgLog((LOG_TRACE, 2, TEXT("Trying to get last frame"))); DWORD dwCtrl = m_dwCtrl; m_dwCtrl &= 0xFFFF0000; m_dwCtrl |= DECODE_DIS; if (S_OK != DecodeNextPicture()) { DbgLog((LOG_ERROR, 2, TEXT("Failed to display last frame"))); } m_dwCtrl = dwCtrl; // Restore } } /*****************************Private*Routine******************************\ * GetDecodeBufferAndFormat * * This function is called by the mpeg decoder when it is time to determine * the colour conversion format and the destination buffer pointer for the * frame. * \**************************************************************************/ LPBYTE CMpegVideoCodec::GetDecodeBufferAndFormat() { unsigned char *pDst; AM_MEDIA_TYPE *pmt; CRefTime tStartTime(m_tStopPrev); CRefTime tStopTime; // Don't ask for a buffer with a negative start time. if (tStartTime < (LONGLONG)0) { tStartTime = (LONGLONG)0; } // // this may block for an indeterminate amount of time // tStopTime = tStartTime + m_seqInfo.tPictureTime; hrCallback = m_pOutput->GetDeliveryBuffer(&m_pOutSample, (REFERENCE_TIME*)&tStartTime, (REFERENCE_TIME*)&tStopTime, 0 // Must set proper flag // if prev frame skipped ); if (FAILED(hrCallback)) { return NULL; } ASSERT(m_pOutSample); hrCallback = m_pOutSample->GetPointer(&pDst); if (FAILED(hrCallback)) { return NULL; } ASSERT(pDst); // // If the media type has changed then pmt is NOT NULL // m_pOutSample->GetMediaType(&pmt); if (pmt != NULL) { CMediaType cmt(*pmt); DeleteMediaType(pmt); SetOutputPinMediaType(&cmt); } return pDst; } /*****************************Private*Routine******************************\ * DecodeNextPicture * * Decodes the next picture stored in the circular buffer. If the picture * is not "skipped" it is passed to the output pin. Updates m_BufferStart, * m_BufferCurr and m_BufferFullness to reflect any emptying of the buffer that * has taken place. * \**************************************************************************/ HRESULT CMpegVideoCodec::DecodeNextPicture() { DWORD rc; HRESULT hr = S_OK; // // ParthaSr. // trying to recover from the absence of quality messages // when I do a skip - so I am predictively skipping // frames. - Snap back to default if I have already // skipped enough frames // if (m_dwLateBy < 750000L) { m_dwCtrl = m_dwCtrlDefault | m_dwQualDefault; } m_VideoControl.dwCtrl = m_dwCtrl; // // If we're within 1 second of the end do all IP // if (m_tStop - m_tStopPrev < CRefTime(1000L)) { // // But are we almost at the end? in which case we need to decode // everything // if (m_tStop - m_tStopPrev < CRefTime(m_seqInfo.tPictureTime) + CRefTime(m_seqInfo.tPictureTime)) { if ((m_dwCtrl & 0xFFFF) != DECODE_DIS) { m_VideoControl.dwCtrl = (m_dwCtrl & 0xFFFF0000) | DECODE_IPB; } } else { if ((m_dwCtrl & 0xFFFF) < DECODE_IP) { m_VideoControl.dwCtrl = (m_dwCtrl & 0xFFFF0000) | DECODE_IP; } } } else { // // Only do IP if we're before the start // if ((m_dwCtrl & 0x3F) > DECODE_IP) { if (m_tStopPrev + CRefTime(m_seqInfo.tPictureTime) + CRefTime(m_seqInfo.tPictureTime) < CRefTime(0L)) { m_VideoControl.dwCtrl = (m_dwCtrl & 0xFFFF0000) | DECODE_IP; } } } DbgLog((LOG_TRACE, 4, TEXT("Decode flags = %X"), m_VideoControl.dwCtrl)); // // Initialize the video codec with the new video data buffer. I am // assuming that there is at least one video frame in the data // buffer. When this function gets called to force out the frames // left in the buffer it needs to guarded with a try except block. // m_VideoControl.pCmprWrite = m_BufferCurr + m_BufferFullness; m_VideoControl.pCmprRead = m_BufferCurr; // // This is where the action starts, call the codec and let it do it stuff. // // Don't die if Windows NT takes away the DCI surface try { MSR_START(m_PerfDecode); rc = m_pVideoDecoder->DecodeFrame(&m_VideoControl); MSR_STOP(m_PerfDecode); } except(EXCEPTION_EXECUTE_HANDLER) { DbgLog((LOG_ERROR, 1, TEXT("Exception in decoder!"))); rc = DECODE_ERR_DATA; } // // Did the frame decode OK // if (rc == DECODE_SUCCESS) { #ifdef DEBUG static char *ft[4] = { "Dummy", "I", "P", "B" }; #endif // // Record frames stats // MSR_INTEGER(m_FrameType, m_VideoControl.dwFrameType); if (m_VideoControl.dwSkipFlag) { DbgLog((LOG_TRACE, 2, TEXT("%hs Frame skipped"), ft[m_VideoControl.dwFrameType])); m_dwFramesSkipped[m_VideoControl.dwFrameType]++; LARGE_INTEGER li; li.QuadPart = m_seqInfo.tPictureTime; if (m_dwLateBy > li.LowPart) { m_dwLateBy -= li.LowPart; } } else { m_dwFramesDecoded[m_VideoControl.dwFrameType]++; } if (m_VideoControl.dwFrameType == FTYPE_I || m_VideoControl.dwFrameType == FTYPE_P) { // // Do I/P - we're actually going to draw the I/P we // decoded last time we got one, not the present one // if (m_NextIP.GetTime((REFERENCE_TIME *)&m_TimeAtLastSyncPoint)) { m_TimeSinceLastSyncPoint = 0; } REFERENCE_TIME t; BOOL bIFrame = m_VideoControl.dwFrameType == FTYPE_I; if (UpdateTimeSyncPoint(m_VideoControl.pFrameStartPos, &t)) { m_NextIP.Set(m_VideoControl.dwSkipFlag, bIFrame, TRUE, t, m_VideoControl.dwTemporalReference); } else { m_NextIP.Set(m_VideoControl.dwSkipFlag, bIFrame, FALSE, t, m_VideoControl.dwTemporalReference); } } else { // // Do B // m_NextIP.NextRef(m_VideoControl.dwTemporalReference); if ( UpdateTimeSyncPoint(m_VideoControl.pFrameStartPos, (REFERENCE_TIME*)&m_TimeAtLastSyncPoint) ) { m_TimeSinceLastSyncPoint = 0; } } CRefTime tStop(m_TimeAtLastSyncPoint + m_TimeSinceLastSyncPoint + m_seqInfo.tPictureTime); if (!m_VideoControl.dwSkipFlag) { LPBITMAPINFOHEADER lpbiDst = HEADER(m_pOutput->CurrentMediaType().Format()); // // if the time is < 0, then this is preroll to get from keyframe to // the current frame. We decompress it into the output buffer but // don't deliver it. // // In order that there are no gaps we actually start the next // frame where we predicted it would start, rather than the // actual synch point // CRefTime tStart(m_tStopPrev); DbgLog((LOG_TRACE, 2, TEXT("%hs Frame decoded - tStart = %s, tStop = %s"), ft[m_VideoControl.dwFrameType], (LPCTSTR)CDisp(tStart), (LPCTSTR)CDisp(tStop))); if (tStop > 0L && tStart <= m_tStop) { // decompressed frames are always key m_pOutSample->SetSyncPoint(TRUE); m_pOutSample->SetActualDataLength(lpbiDst->biSizeImage); m_pOutSample->SetTime((REFERENCE_TIME*)&tStart, (REFERENCE_TIME*)&tStop); DbgLog((LOG_TRACE, 2, TEXT("%hs Frame sent to next filter"), ft[m_VideoControl.dwFrameType])); hr = m_pOutput->Deliver(m_pOutSample); MSR_NOTE(m_FrameDrawn); } } // // We have successfully decoded a frame. // Set the new position // m_tStopPrev = tStop; UpdateBufferPosition(m_VideoControl.pCmprRead); m_TimeSinceLastSyncPoint += m_seqInfo.tPictureTime; } else if (rc == DECODE_ERR_QUARTZ) { DbgLog((LOG_ERROR, 2, TEXT("Could not get buffer from down stream filter"))); hr = hrCallback; } else if (rc == DECODE_ERR_DATA) { // // We did not have enough data available to decode the // current frame, so save the data for next time. // // Since this can only happen at a discontinuity, end of // stream or undecipherable data we'll just throw the data // away if it can't be eaten DbgLog((LOG_ERROR, 2, TEXT("Buffer underflow") )); UpdateBufferPosition(m_VideoControl.pCmprRead); // // Notify the filter graph of stream errors // NotifyEvent(EC_STREAM_ERROR_STILLPLAYING, hr, 0); } else { // // Some sort of error occurred, throw the remainder of the // buffer away and skip this packet. // DbgLog((LOG_ERROR, 2, TEXT("Bad return code %d from MediaMatics video codec!"), rc )); UpdateBufferPosition(m_VideoControl.pCmprRead); // // Notify the filter graph of stream errors // NotifyEvent(EC_STREAM_ERROR_STILLPLAYING, hr, 0); } // // release the output buffer. If the connected pin still needs it, // it will have addrefed it itself. // if (m_pOutSample != NULL) { m_pOutSample->Release(); m_pOutSample = NULL; } return hr; } /*****************************Private*Routine******************************\ * CopySampleToBuffer * * Copies the sample to the input buffer and returns the number of bytes * present in the buffer. Updates m_BufferStart, m_BufferCurr and * m_BufferFullness to reflect any wrapping in the buffer * that may have occurred. * \**************************************************************************/ HRESULT CMpegVideoCodec::CopySampleToBuffer( IMediaSample *pSample ) { BYTE *pSrc; long LenLeftInBuffer; long AmountToCopy; HRESULT hr; if (m_pSample == NULL) { hr = pSample->GetPointer(&pSrc); if (FAILED(hr)) { return hr; } ASSERT(pSrc); // Skip pass the stream header and extract the packet length. // if (m_bPayloadOnly) { m_pSample = pSrc; m_LenLeftInPacket = pSample->GetActualDataLength(); } else { m_pSample = SkipToPacketData(pSrc, m_LenLeftInPacket); if (m_pSample == NULL) { return E_INVALIDARG; } } } // // Move what remains in the video data buffer to the top of the buffer // and append the new video data to it. // Don't ever let it fill up completely or we'll have to worry about // full vs empty // LenLeftInBuffer = m_VBlockSize - m_BufferFullness; AmountToCopy = min(LenLeftInBuffer, m_LenLeftInPacket); CopyMemory(m_BufferCurr + m_BufferFullness, m_pSample, AmountToCopy); m_LenLeftInPacket -= AmountToCopy; m_pSample += AmountToCopy; m_BufferFullness += AmountToCopy; return S_OK; } /*****************************Private*Routine******************************\ * UpdateBufferPosition * * Updates m_BufferCurr from the new position passed in. * Checks if m_BufferCurr is in the 'virtual' space at the end of the buffer * and if it is adjusts m_BufferCurrent and m_BufferFullness down by the real * buffer size * \**************************************************************************/ void CMpegVideoCodec::UpdateBufferPosition( LPBYTE lpNewCurrent ) { ASSERT(lpNewCurrent >= m_BufferCurr); // // Sometimes the position gets reported as 1 too many (!) // if (lpNewCurrent > m_BufferCurr + m_BufferFullness) { lpNewCurrent = m_BufferCurr + m_BufferFullness; } m_BufferFullness -= lpNewCurrent - m_BufferCurr; if (lpNewCurrent >= (m_BufferStart + m_VBlockSize)) { lpNewCurrent -= m_VBlockSize; } // // Fix up the time code list // while (TRUE) { POSITION pos = m_PtsQueue.GetHeadPosition(); if (pos == NULL) { break; } CTimePosition *pTimePos = m_PtsQueue.Get(pos); if (BuffOffset(pTimePos->m_BufferPosition) < BuffOffset(lpNewCurrent)) { delete pTimePos; m_PtsQueue.RemoveHead(); } else { break; } } // // Advance to the new position // // m_BufferCurr = lpNewCurrent; MoveMemory(m_BufferCurr, lpNewCurrent, m_BufferFullness); } /*****************************Private*Routine******************************\ * UpdateTimeSyncPoint * * Each time we get a media sample with a Pts * time stamp we add a TIMEPOSITION entry to a queue of time positions. * Each time we decode an I frame we record the starting and ending position * of the I frame picture within the input buffer, this information is * then used to find a suitable time stamp to associate with the frame. If * a suitable time stamp cannot be found or the frame is not an I frame we * calculate a suitable time code by extrapolation. * \**************************************************************************/ BOOL CMpegVideoCodec::UpdateTimeSyncPoint( LPBYTE lpPicStart, REFERENCE_TIME *Time ) { POSITION pos = m_PtsQueue.GetHeadPosition(); BOOL bFound; for (bFound = FALSE; pos != NULL; ) { CTimePosition *pTimePos = m_PtsQueue.GetNext(pos); if (BuffOffset(pTimePos->m_BufferPosition) <= BuffOffset(lpPicStart)) { // Buffer start time stamp could be for us (but keep looking // in case there's a better one) // // NOTE this ASSUMES there are no packets with time stamps but no // start code so in fact this one should be ours bFound = TRUE; *Time = pTimePos->m_PtsTimeStamp; delete pTimePos; m_PtsQueue.RemoveHead(); } else { break; } } if (bFound) { DbgLog((LOG_TRACE, 3, TEXT("CMpegVideoCodec::UpdateTimeSyncPoint() : Found time %s"), (LPCTSTR)CDisp(*Time) )); } return bFound; } /*****************************Private*Routine******************************\ * BuffOffset * * Adjusts the supplied offset so that is based upon m_BufferCurr * \**************************************************************************/ inline ptrdiff_t CMpegVideoCodec::BuffOffset( LPBYTE Offset ) { ptrdiff_t x = Offset - m_BufferCurr; if (x < 0) { x += m_VBlockSize; } return x; } /******************************Public*Routine******************************\ * CheckInputType * * Check if you can support mtIn * \**************************************************************************/ HRESULT CMpegVideoCodec::CheckInputType( const CMediaType* pmtIn ) { DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::CheckInputType"))); // // Check for native streams // if (*pmtIn->Type() == MEDIATYPE_Stream && *pmtIn->Subtype() == MEDIASUBTYPE_MPEG1Video) { // // If there's no format block we'll read the stream during // CompleteConnect() // if (pmtIn->cbFormat == 0) { return S_OK; } } else { // // check this is an MPEG video format type // if (*pmtIn->FormatType() != FORMAT_MPEGVideo) { return E_INVALIDARG; } // // we only support MEDIATYPE_Video // if (*pmtIn->Type() != MEDIATYPE_Video) { return E_INVALIDARG; } if (*pmtIn->Subtype() != MEDIASUBTYPE_MPEG1Packet && *pmtIn->Subtype() != MEDIASUBTYPE_MPEG1Payload) { return E_INVALIDARG; } } if (pmtIn->cbFormat < SIZE_VIDEOHEADER + sizeof(DWORD) || pmtIn->cbFormat < SIZE_MPEG1VIDEOINFO((MPEG1VIDEOINFO *)pmtIn->pbFormat)) { return E_INVALIDARG; } // // Check the sequence header and save the info // MPEG1VIDEOINFO* videoInfo = (MPEG1VIDEOINFO *)pmtIn->pbFormat; if (!ParseSequenceHeader(videoInfo->bSequenceHeader, videoInfo->cbSequenceHeader, &m_seqInfo)) { return E_INVALIDARG; } return S_OK; } /******************************Public*Routine******************************\ * CheckTransform * * Check if you can support the transform from this input to this output * \**************************************************************************/ HRESULT CMpegVideoCodec::CheckTransform( const CMediaType* pmtIn, const CMediaType* pmtOut ) { DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::CheckTransform"))); // we only output video if (*pmtOut->Type() != MEDIATYPE_Video) { return VFW_E_TYPE_NOT_ACCEPTED; } // Check there is a format block if (*pmtOut->FormatType() != FORMAT_VideoInfo) { return VFW_E_TYPE_NOT_ACCEPTED; } // // See if we can use dci/direct draw. // First check that there is a non empty target rectangle. // VIDEOINFO *videoInfo = (VIDEOINFO *)pmtOut->pbFormat; if (!IsRectEmpty(&videoInfo->rcTarget)) { // // Next, check that the source rectangle is the entire movie. // if ( videoInfo->rcSource.left == 0 && videoInfo->rcSource.top == 0 && videoInfo->rcSource.right == m_seqInfo.lWidth && videoInfo->rcSource.bottom == m_seqInfo.lHeight) { // // Now check that the target rectangles size is the same as // the movies, that is there is no stretching or shrinking. // if ( (videoInfo->rcTarget.right - videoInfo->rcTarget.left) == m_seqInfo.lWidth && (videoInfo->rcTarget.bottom - videoInfo->rcTarget.top) == m_seqInfo.lHeight) { #ifndef _X86_ // On Risc machines make sure we are DWORD aligned if ((videoInfo->rcTarget.left & 0x03) == 0x00) #endif { DbgLog((LOG_TRACE, 2, TEXT("Using DCI"))); return S_OK; } } } DbgLog((LOG_TRACE, 2, TEXT("NOT Using DCI"))); return E_FAIL; } return S_OK; } /******************************Public*Routine******************************\ * SetMediaType * * Overriden to know when the media type is actually set * \**************************************************************************/ HRESULT CMpegVideoCodec::SetMediaType( PIN_DIRECTION direction, const CMediaType *pmt ) { DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::SetMediaType"))); if (direction == PINDIR_INPUT) { // // Get the data type // if (*pmt->Subtype() == MEDIASUBTYPE_MPEG1Packet) { m_bPayloadOnly = FALSE; } else { ASSERT(*pmt->Subtype() == MEDIASUBTYPE_MPEG1Payload || *pmt->Subtype() == MEDIASUBTYPE_MPEG1Video); m_bPayloadOnly = TRUE; } } else { SetOutputPinMediaType(pmt); } return S_OK; } /*****************************Private*Routine******************************\ * SetOutputPinMediaType * * This function is a static member function of CMpegVideoCodec, this is so that * it can be called from SetMediaType (above) and from the static member * function GetDecodeBufferAndFormat. Note that we pass in the "this" pointer * explicitly. * \**************************************************************************/ void CMpegVideoCodec::SetOutputPinMediaType( const CMediaType *pmt ) { VIDEOINFO *pvi; LONG lStride; LONG lOffset; if (*pmt->Subtype() == MEDIASUBTYPE_Y41P) { m_dwOutputFormatDib = MM_411PK; m_dwOutputFormatDdb = MM_411PK; } else if (*pmt->Subtype() == MEDIASUBTYPE_YUY2) { m_dwOutputFormatDib = MM_422PK; m_dwOutputFormatDdb = MM_422PK; } else if (*pmt->Subtype() == MEDIASUBTYPE_UYVY) { m_dwOutputFormatDib = MM_422SPK; m_dwOutputFormatDdb = MM_422SPK; } else if (*pmt->Subtype() == MEDIASUBTYPE_RGB24) { m_dwOutputFormatDib = MM_RGB24_DIB; m_dwOutputFormatDdb = MM_RGB24_DDB; } else if (*pmt->Subtype() == MEDIASUBTYPE_RGB565) { m_dwOutputFormatDib = MM_RGB565_DIB; m_dwOutputFormatDdb = MM_RGB565_DDB; } else if (*pmt->Subtype() == MEDIASUBTYPE_RGB555) { m_dwOutputFormatDib = MM_RGB555_DIB; m_dwOutputFormatDdb = MM_RGB555_DDB; } else { ASSERT(*pmt->Subtype() == MEDIASUBTYPE_RGB8); if (m_PaletteType == COLOUR_PALETTE) { m_dwOutputFormatDib = MM_RGB8_DIB; m_dwOutputFormatDdb = MM_RGB8_DDB; } else { m_dwOutputFormatDib = MM_Y_DIB; m_dwOutputFormatDdb = MM_Y_DDB; } } // // lStride is the distance between in bytes between a pel on the // screen and the pel directly underneath it. // pvi = (VIDEOINFO *)pmt->pbFormat; lStride = ((pvi->bmiHeader.biWidth * pvi->bmiHeader.biBitCount) + 7) / 8; lStride = (lStride + 3) & ~3; // // lOffset is the distance in bytes from the top corner of the // target bitmap to the top corner of the video image. When we are // using DIBs this value allways be zero. // // When we are using DCI/DirectDraw this value will only be zero if // we are drawing the video image at the top left hand corner of the // display. // lOffset = (((pvi->rcTarget.left * pvi->bmiHeader.biBitCount) + 7) / 8) + (pvi->rcTarget.top * lStride); m_VideoControl.dwOutStride = lStride; m_VideoControl.dwOutOffset = lOffset; // // See what orientation we need to use when colour converting // the frame. // if (pvi->bmiHeader.biHeight > 0) { m_VideoControl.dwOutputFormat = m_dwOutputFormatDib; } else { m_VideoControl.dwOutputFormat = m_dwOutputFormatDdb; } } /******************************Public*Routine******************************\ * GetMediaType * * Return our preferred output media types (in order) * \**************************************************************************/ HRESULT CMpegVideoCodec::GetMediaType( int iPosition, CMediaType *pmt ) { VIDEOINFO *pVideoInfo; CMediaType cmt; DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::GetMediaType"))); if (iPosition < 0) { return E_INVALIDARG; } // // Quick hack to enable greyscale for Robin // if (m_PaletteType == GREY_PALETTE) { if (iPosition > 0) { return VFW_S_NO_MORE_ITEMS; } iPosition = MT_RGB8; } // // We copy the proposed output format so that we can play around with // it all we like and still leave the original preferred format // untouched. We try each of the known BITMAPINFO types in turn // starting off with the best quality moving through to the worst // (palettised) format // cmt = m_pInput->CurrentMediaType(); if (*cmt.Type() != MEDIATYPE_Video) { pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_PREHEADER); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } pVideoInfo->rcSource.top = 0; pVideoInfo->rcSource.left = 0; pVideoInfo->rcSource.right = m_seqInfo.lWidth; pVideoInfo->rcSource.bottom = m_seqInfo.lHeight; pVideoInfo->AvgTimePerFrame = m_seqInfo.tPictureTime; pVideoInfo->rcTarget = pVideoInfo->rcSource; } else { pVideoInfo = (VIDEOINFO *) cmt.Format(); ASSERT(pVideoInfo != NULL); } // // Fill in the output format according to requested position, see the // Media Type enum in mpgvideo.h for the list of supported types and // their positions. // switch (iPosition) { case MT_Y41P: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, MAKEFOURCC('Y','4','1','P'), 12); *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_Y41P); break; case MT_YUY2: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, MAKEFOURCC('Y','U','Y','2'), 16); *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_YUY2); break; case MT_UYVY: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, MAKEFOURCC('U','Y','V','Y'), 16); *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_UYVY); break; case MT_RGB24: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, BI_RGB, 24); *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_RGB24); break; case MT_RGB565: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER + SIZE_MASKS); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, BI_BITFIELDS, 16); DWORD *pdw; pdw = (DWORD *)(HEADER(pVideoInfo) + 1); pdw[iRED] = bits565[iRED]; pdw[iGREEN] = bits565[iGREEN]; pdw[iBLUE] = bits565[iBLUE]; *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_RGB565); break; case MT_RGB555: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer(SIZE_VIDEOHEADER); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, BI_RGB, 16); *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_RGB555); break; case MT_RGB8: pVideoInfo = (VIDEOINFO *)cmt.ReallocFormatBuffer( SIZE_VIDEOHEADER + SIZE_PALETTE); if (pVideoInfo == NULL) { return E_OUTOFMEMORY; } InitDestinationVideoInfo(pVideoInfo, BI_RGB, 8); InitDestinationPalette(pVideoInfo); *pmt = cmt; pmt->SetSubtype(&MEDIASUBTYPE_RGB8); break; default: return VFW_S_NO_MORE_ITEMS; } // // This block assumes that lpbi has been set up to point to a valid // bitmapinfoheader and that cmt has been copied into *pmt. // This is taken care of in the switch statement above. This should // kept in mind when new formats are added. // pmt->SetType(&MEDIATYPE_Video); pmt->SetFormatType(&FORMAT_VideoInfo); // // we assume the output format is uncompressed // pmt->SetTemporalCompression(FALSE); pmt->SetSampleSize(HEADER(pVideoInfo)->biSizeImage); return S_OK; } /*****************************Private*Routine******************************\ * InitDestinationVideoInfo * * Fills in common video and bitmap info header fields * \**************************************************************************/ void CMpegVideoCodec::InitDestinationVideoInfo( VIDEOINFO *pVideoInfo, DWORD dwComppression, int nBitCount ) { LPBITMAPINFOHEADER lpbi = HEADER(pVideoInfo); lpbi->biSize = sizeof(BITMAPINFOHEADER); lpbi->biWidth = m_seqInfo.lWidth; lpbi->biHeight = m_seqInfo.lHeight; lpbi->biPlanes = 1; lpbi->biBitCount = nBitCount; lpbi->biXPelsPerMeter = 0; lpbi->biYPelsPerMeter = 0; lpbi->biCompression = dwComppression; lpbi->biSizeImage = GetBitmapSize(lpbi); // // The "bit" rate is image size in bytes times 8 (to convert to bits) // divided by the AvgTimePerFrame. This result is in bits per 100 nSec, // so we multiply by 10000000 to convert to bits per second, this multiply // is combined with "times" 8 above so the calculations becomes: // // BitRate = (biSizeImage * 80000000) / AvgTimePerFrame // LARGE_INTEGER li; li.QuadPart = pVideoInfo->AvgTimePerFrame; pVideoInfo->dwBitRate = MulDiv(lpbi->biSizeImage, 80000000, li.LowPart); pVideoInfo->dwBitErrorRate = 0L; } /*****************************Private*Routine******************************\ * InitDestinationPalette * * Creates a standard colour palette or gray scale palette as determined * by the PaletteType parameter. * \**************************************************************************/ void CMpegVideoCodec::InitDestinationPalette( VIDEOINFO *pVideoInfo ) { int i; int iPalLowEnd; int iPalHiStart; PALETTEENTRY pal[24]; HDC hDC; if (m_PaletteType == COLOUR_PALETTE) { for ( i = 0; i < 256; i++ ) { pVideoInfo->bmiColors[i].rgbRed = PaletteData[i].r; pVideoInfo->bmiColors[i].rgbBlue = PaletteData[i].b; pVideoInfo->bmiColors[i].rgbGreen = PaletteData[i].g; pVideoInfo->bmiColors[i].rgbReserved = 0; } iPalLowEnd = 16; iPalHiStart = 232; } else { for ( i = 0; i < 256; i++ ) { pVideoInfo->bmiColors[i].rgbRed = i; pVideoInfo->bmiColors[i].rgbBlue = i; pVideoInfo->bmiColors[i].rgbGreen = i; pVideoInfo->bmiColors[i].rgbReserved = 0; } iPalLowEnd = 10; iPalHiStart = 246; } hDC = GetDC(GetDesktopWindow()); GetSystemPaletteEntries(hDC, 0, 16, &pal[0] ); for ( i = 0; i < iPalLowEnd; i++ ) { pVideoInfo->bmiColors[i].rgbRed = pal[i].peRed; pVideoInfo->bmiColors[i].rgbGreen = pal[i].peGreen; pVideoInfo->bmiColors[i].rgbBlue = pal[i].peBlue; } GetSystemPaletteEntries(hDC, iPalHiStart, 256 - iPalHiStart, &pal[0] ); for ( i = iPalHiStart; i < 256; i++ ) { pVideoInfo->bmiColors[i].rgbRed = pal[i - iPalHiStart].peRed; pVideoInfo->bmiColors[i].rgbGreen = pal[i - iPalHiStart].peGreen; pVideoInfo->bmiColors[i].rgbBlue = pal[i - iPalHiStart].peBlue; } ReleaseDC(GetDesktopWindow(), hDC); HEADER(pVideoInfo)->biClrUsed = 256; HEADER(pVideoInfo)->biClrImportant = 0; } /******************************Public*Routine******************************\ * DecideBufferSize * * Called from CBaseOutputPin to prepare the allocator's count * of buffers and sizes * \**************************************************************************/ HRESULT CMpegVideoCodec::DecideBufferSize( IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES * pProperties ) { DbgLog((LOG_TRACE, 2, TEXT("CMpegVideoCodec::DecideBufferSize"))); ASSERT(pAllocator); ASSERT(pProperties); HRESULT hr = NOERROR; pProperties->cBuffers = 1; pProperties->cbBuffer = m_pOutput->CurrentMediaType().GetSampleSize(); ASSERT(pProperties->cbBuffer); DbgLog((LOG_TRACE, 2, TEXT("Sample size = %ld\n"), pProperties->cbBuffer)); // Ask the allocator to reserve us some sample memory, NOTE the function // can succeed (that is return NOERROR) but still not have allocated the // memory that we requested, so we must check we got whatever we wanted ALLOCATOR_PROPERTIES Actual; hr = pAllocator->SetProperties(pProperties,&Actual); if (FAILED(hr)) { return hr; } ASSERT(Actual.cbAlign == 1); ASSERT(Actual.cbPrefix == 0); if (Actual.cbBuffer < pProperties->cbBuffer || Actual.cBuffers < pProperties->cBuffers) { // can't use this allocator return E_INVALIDARG; } return S_OK; } /******************************Public*Routine******************************\ * StartStreaming * * * \**************************************************************************/ HRESULT CMpegVideoCodec::StartStreaming( void ) { CAutoLock lock(&m_csFilter); long Size; // HRESULT hr; ASSERT(MEDIASUBTYPE_RGB8 == *m_pOutput->CurrentMediaType().Subtype() || MEDIASUBTYPE_RGB555 == *m_pOutput->CurrentMediaType().Subtype() || MEDIASUBTYPE_RGB565 == *m_pOutput->CurrentMediaType().Subtype() || MEDIASUBTYPE_Y41P == *m_pOutput->CurrentMediaType().Subtype() || MEDIASUBTYPE_YUY2 == *m_pOutput->CurrentMediaType().Subtype() || MEDIASUBTYPE_UYVY == *m_pOutput->CurrentMediaType().Subtype() || MEDIASUBTYPE_RGB24 == *m_pOutput->CurrentMediaType().Subtype()); ASSERT(m_pFrameBuff == NULL); ASSERT(m_pVideoDecoder == NULL); ASSERT(m_Buffer == NULL); // // Create the video codec - if we fail to open the codec it is // probably because the codec was unable to allocate memory for its // decoding tables. // m_pVideoDecoder = new CVideoDecoder(this); if (m_pVideoDecoder == NULL) { return E_OUTOFMEMORY; } // // Allocate some space for the codecs I and P frame buffer store // Size = m_seqInfo.lWidth * m_seqInfo.lHeight; m_pFrameBuff = new BYTE[ 3 * (Size + (2 * (Size / 4)))]; if (m_pFrameBuff == NULL) { delete m_pVideoDecoder; m_pVideoDecoder = NULL; return E_OUTOFMEMORY; } m_VBlockSize = m_seqInfo.lvbv; m_VBlockSize += ((1 << 16) - 1); m_VBlockSize &= ~((1 << 16) - 1); // m_Buffer = new CCircularBuffer(m_VBlockSize, m_VBlockSize, hr); m_Buffer = new BYTE[m_VBlockSize]; if (m_Buffer == NULL) { delete [] m_pFrameBuff; m_pFrameBuff = NULL; delete m_pVideoDecoder; m_pVideoDecoder = NULL; return E_OUTOFMEMORY; } // // Initialize the mpeg video codec // m_dwLateBy = 0; m_dwCtrl = (m_dwCtrlDefault | m_dwQualDefault); m_VideoControl.dwCtrl = m_dwCtrl; m_VideoControl.dwYStride = m_seqInfo.lWidth; m_VideoControl.dwYLines = m_seqInfo.lHeight; ZeroMemory(m_pFrameBuff, Size); m_VideoControl.pFrameBuff = m_pFrameBuff; // // Now reset the mpeg video decoder // ResetVideoDecoder(); // // Reset frame stats // ZeroMemory(m_dwFramesSkipped, sizeof(m_dwFramesSkipped)); ZeroMemory(m_dwFramesDecoded, sizeof(m_dwFramesDecoded)); return S_OK; } /******************************Public*Routine******************************\ * StopStreaming * * * \**************************************************************************/ HRESULT CMpegVideoCodec::StopStreaming( void ) { CAutoLock lock(&m_csFilter); CAutoLock lck(&m_csReceive); CTimePosition *pTimePos; ASSERT(m_pFrameBuff != NULL); ASSERT(m_pVideoDecoder != NULL); ASSERT(m_Buffer != NULL); delete m_pVideoDecoder; m_pVideoDecoder = NULL; delete [] m_pFrameBuff; m_pFrameBuff = NULL; delete [] m_Buffer; m_Buffer = NULL; // // Purge the PTS queue. // DbgLog((LOG_TRACE, 2, TEXT("Freeing %d time entries"), m_PtsQueue.GetCount())); while( (pTimePos = m_PtsQueue.RemoveHead()) != NULL) { delete pTimePos; } return S_OK; } /******************************Public*Routine******************************\ * Notify * * Handle quality control notifications sent to us * \**************************************************************************/ HRESULT CMpegVideoCodec::AlterQuality( Quality q ) { DWORD dwNewCtrl; LARGE_INTEGER li; li.QuadPart = q.Late; DWORD dwTimeLate = li.LowPart; MSR_INTEGER(m_QualMsg, dwTimeLate); li.QuadPart = m_seqInfo.tPictureTime; DbgLog((LOG_TRACE, 2, TEXT("Q = %s Prop = %4.4ld Late = %8.8ld Fram = %8.8ld"), q.Type == Famine ? TEXT("Famine") : TEXT("Flood "), q.Proportion, dwTimeLate, li.LowPart)); // // See if the user has overidden quality messages // if (m_IgnoreQualityMessage) { return S_OK; } // // Turn off the old option and then bring in the new. // m_dwLateBy = dwTimeLate; if (dwTimeLate <= 750000L) { DbgLog((LOG_TRACE, 2, TEXT("Default QL"))); dwNewCtrl = m_dwCtrlDefault; } else if (dwTimeLate <= (7L * 330000L)) { DbgLog((LOG_TRACE, 2, TEXT("IP"))); dwNewCtrl = DECODE_IP; } else { DbgLog((LOG_TRACE, 2, TEXT("I"))); dwNewCtrl = DECODE_I; } m_dwCtrl = (m_dwQualDefault | dwNewCtrl); return S_OK; } // ------------------------------------------------------------------------- // IP Tracking class // ------------------------------------------------------------------------- // inline CMpegVideoCodec::CNextIP::CNextIP() { Reset(); } inline void CMpegVideoCodec::CNextIP::Set( DWORD dwSkipFlag, BOOL bIFrame, BOOL bTimeSet, REFERENCE_TIME t, DWORD dwTemporalReference ) { if (m_bGotFirst) { m_bTimeToDraw = FALSE; NextRef(dwTemporalReference); // Might already be time if no Bs } else { if (bIFrame) { m_bGotFirst = TRUE; m_bTimeToDraw = TRUE; } } m_dwSkipFlag = dwSkipFlag; m_bTimeSet = bTimeSet; m_t = t; m_dwTemporalReference = dwTemporalReference; } inline BOOL CMpegVideoCodec::CNextIP::GotFirst() { return m_bGotFirst; } inline void CMpegVideoCodec::CNextIP::NextRef( DWORD dwTemporalReference ) { // See if we're next after this one // Don't set m_bTimeToDraw = FALSE here because in the case // where we're stepping through undrawable B-frames before the // first I-Frame we've already set m_bTimeToDraw = TRUE if (((dwTemporalReference + 1) & 1023) == m_dwTemporalReference) { m_bTimeToDraw = TRUE; } DbgLog((LOG_TRACE, 3, TEXT("New Temporal Reference %d, NextIP = %d, TimeToDraw = %d"), dwTemporalReference, m_dwTemporalReference, m_bTimeToDraw)); } inline BOOL CMpegVideoCodec::CNextIP::GetTime( REFERENCE_TIME *pt ) { if (m_bTimeSet) { *pt = m_t; } return m_bTimeSet; } inline BOOL CMpegVideoCodec::CNextIP::TimeToDraw() const { return !m_dwSkipFlag && m_bTimeToDraw; } inline void CMpegVideoCodec::CNextIP::Reset() { m_bGotFirst = FALSE; m_bTimeSet = FALSE; m_dwTemporalReference = 0; m_bTimeToDraw = FALSE; } /******************************Public*Routine******************************\ * SkipToPacketData * * * \**************************************************************************/ LPBYTE SkipToPacketData( LPBYTE pSrc, long &LenLeftInPacket ) { LPBYTE lpPacketStart; DWORD bData; long Length; // // Skip the stream ID and extract the packet length // pSrc += 4; bData = *pSrc++; Length = (long)((bData << 8) + *pSrc++); DbgLog((LOG_TRACE, 3, TEXT("Packet length %ld"), Length )); // // Record position of first byte after packet length // lpPacketStart = pSrc; // // Remove stuffing bytes // for (; ; ) { bData = *pSrc++; if (!(bData & 0x80)) { break; } } if ((bData & 0xC0) == 0x40) { pSrc++; bData = *pSrc++; } switch (bData & 0xF1) { case 0x21: pSrc += 4; break; case 0x31: pSrc += 9; break; default: if (bData != 0x0F) { DbgLog((LOG_TRACE, 2, TEXT("Invalid packet - 0x%2.2X\n"), bData)); return NULL; } } // // The length left in the packet is the original length of the packet // less those bytes that we have just skipped over. // LenLeftInPacket = Length - (pSrc - lpPacketStart); return pSrc; } #ifdef DEBUG LPCTSTR PictureTypes[8] = { TEXT("forbidden frame type"), TEXT("I-Frame"), TEXT("P-Frame"), TEXT("B-Frame"), TEXT("D-Frame"), TEXT("Reserved frame type"), TEXT("Reserved frame type"), TEXT("Reserved frame type") }; LPCTSTR PelAspectRatios[16] = { TEXT("Forbidden"), TEXT("1.0000 - VGA etc"), TEXT("0.6735"), TEXT("0.7031 - 16:9, 625 line"), TEXT("0.7615"), TEXT("0.8055"), TEXT("0.8437 - 16:9, 525 line"), TEXT("0.8935"), TEXT("0.9375 - CCIR601, 625 line"), TEXT("0.9815"), TEXT("1.0255"), TEXT("1.0695"), TEXT("1.1250 - CCIR601, 525 line"), TEXT("1.1575"), TEXT("1.2015"), TEXT("Reserved") }; LPCTSTR PictureRates[16] = { TEXT("Forbidden"), TEXT("23.976"), TEXT("24"), TEXT("25"), TEXT("29.97"), TEXT("30"), TEXT("50"), TEXT("59.94"), TEXT("60"), TEXT("Reserved"), TEXT("Reserved"), TEXT("Reserved"), TEXT("Reserved"), TEXT("Reserved"), TEXT("Reserved"), TEXT("Reserved") }; #endif // DEBUG const LONG PictureTimes[16] = { 0, (LONG)((double)10000000 / 23.976), (LONG)((double)10000000 / 24), (LONG)((double)10000000 / 25), (LONG)((double)10000000 / 29.97), (LONG)((double)10000000 / 30), (LONG)((double)10000000 / 50), (LONG)((double)10000000 / 59.94), (LONG)((double)10000000 / 60) }; const LONG AspectRatios[16] = { 0, 393700, (LONG)(393700.0 * 0.6735), (LONG)(393700.0 * 0.7031), (LONG)(393700.0 * 0.7615), (LONG)(393700.0 * 0.8055), (LONG)(393700.0 * 0.8437), (LONG)(393700.0 * 0.8935), (LONG)(393700.0 * 0.9375), (LONG)(393700.0 * 0.9815), (LONG)(393700.0 * 1.0255), (LONG)(393700.0 * 1.0695), (LONG)(393700.0 * 1.1250), (LONG)(393700.0 * 1.1575), (LONG)(393700.0 * 1.2015), 0 }; /******************************Public*Routine******************************\ * ParseSequenceHeader * * * \**************************************************************************/ BOOL CMpegVideoCodec::ParseSequenceHeader( const BYTE *pbData, LONG lData, SEQHDR_INFO *pInfo ) { ASSERT(*(UNALIGNED DWORD *)pbData == ByteSwap(SEQUENCE_HEADER_CODE)); // // Check random marker bit // if (!(pbData[10] & 0x20)) { DbgLog((LOG_ERROR, 2, TEXT("Sequence header invalid marker bit"))); return FALSE; } DWORD dwWidthAndHeight = ((DWORD)pbData[4] << 16) + ((DWORD)pbData[5] << 8) + ((DWORD)pbData[6]); pInfo->lWidth = dwWidthAndHeight >> 12; pInfo->lHeight = dwWidthAndHeight & 0xFFF; DbgLog((LOG_TRACE, 2, TEXT("Width = %d, Height = %d"), pInfo->lWidth, pInfo->lHeight)); // // the '8' bit is the scramble flag used by sigma designs - ignore // BYTE PelAspectRatioAndPictureRate = pbData[7]; if ((PelAspectRatioAndPictureRate & 0x0F) > 8) { PelAspectRatioAndPictureRate &= 0xF7; } DbgLog((LOG_TRACE, 2, TEXT("Pel Aspect Ratio = %s"), PelAspectRatios[PelAspectRatioAndPictureRate >> 4])); DbgLog((LOG_TRACE, 2, TEXT("Picture Rate = %s"), PictureRates[PelAspectRatioAndPictureRate & 0x0F])); if ((PelAspectRatioAndPictureRate & 0xF0) == 0 || (PelAspectRatioAndPictureRate & 0x0F) == 0) { DbgLog((LOG_ERROR, 2, TEXT("Sequence header invalid ratio/rate"))); return FALSE; } pInfo->tPictureTime = (LONGLONG)PictureTimes[PelAspectRatioAndPictureRate & 0x0F]; pInfo->lTimePerFrame = MulDiv((LONG)pInfo->tPictureTime, 9, 1000); /* Pull out the bit rate and aspect ratio for the type */ pInfo->dwBitRate = ((((DWORD)pbData[8] << 16) + ((DWORD)pbData[9] << 8) + (DWORD)pbData[10]) >> 6); if (pInfo->dwBitRate == 0x3FFFF) { DbgLog((LOG_TRACE, 2, TEXT("Variable video bit rate"))); pInfo->dwBitRate = 0; } else { pInfo->dwBitRate *= 400; DbgLog((LOG_TRACE, 2, TEXT("Video bit rate is %d bits per second"), pInfo->dwBitRate)); } // // Get a DC // HDC hdc = GetDC(GetDesktopWindow()); ASSERT(hdc != NULL); // // Guess (randomly) 39.37 inches per meter // LONG lNotionalPelsPerMeter = MulDiv((LONG)GetDeviceCaps(hdc, LOGPIXELSX), 3937, 100); pInfo->lXPelsPerMeter = lNotionalPelsPerMeter; pInfo->lYPelsPerMeter = MulDiv( lNotionalPelsPerMeter, AspectRatios[PelAspectRatioAndPictureRate >> 4], 10000); // // Pull out the vbv // pInfo->lvbv = ((((LONG)pbData[10] & 0x1F) << 5) | ((LONG)pbData[11] >> 3)) * 2048; DbgLog((LOG_TRACE, 2, TEXT("vbv size is %d bytes"), pInfo->lvbv)); // // Check constrained parameter stuff // if (pbData[11] & 0x04) { DbgLog((LOG_TRACE, 2, TEXT("Constrained parameter video stream"))); if (pInfo->lvbv > 40960) { DbgLog((LOG_ERROR, 1, TEXT("Invalid vbv (%d) for Constrained stream"), pInfo->lvbv)); // // Have to let this through too! bisp.mpg has this // But constrain it since it might be random // pInfo->lvbv = 40960; } } else { DbgLog((LOG_TRACE, 2, TEXT("Non-Constrained parameter video stream"))); } pInfo->lActualHeaderLen = lData; CopyMemory((PVOID)pInfo->RawHeader, (PVOID)pbData, pInfo->lActualHeaderLen); return TRUE; } /******************************Public*Routine******************************\ * exported entry points for registration and * unregistration (in this case they only call * through to default implmentations). * * * * History: * \**************************************************************************/ STDAPI DllRegisterServer() { return AMovieDllRegisterServer2( TRUE ); } STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2( FALSE ); }