| //------------------------------------------------------------------------------ | |
| // File: Vtrans.cpp | |
| // | |
| // Desc: DirectShow base classes. | |
| // | |
| // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. | |
| //------------------------------------------------------------------------------ | |
| #include <streams.h> | |
| #include <measure.h> | |
| // #include <vtransfr.h> // now in precomp file streams.h | |
| CVideoTransformFilter::CVideoTransformFilter | |
| ( __in_opt LPCTSTR pName, __inout_opt LPUNKNOWN pUnk, REFCLSID clsid) | |
| : CTransformFilter(pName, pUnk, clsid) | |
| , m_itrLate(0) | |
| , m_nKeyFramePeriod(0) // No QM until we see at least 2 key frames | |
| , m_nFramesSinceKeyFrame(0) | |
| , m_bSkipping(FALSE) | |
| , m_tDecodeStart(0) | |
| , m_itrAvgDecode(300000) // 30mSec - probably allows skipping | |
| , m_bQualityChanged(FALSE) | |
| { | |
| #ifdef PERF | |
| RegisterPerfId(); | |
| #endif // PERF | |
| } | |
| CVideoTransformFilter::~CVideoTransformFilter() | |
| { | |
| // nothing to do | |
| } | |
| // Reset our quality management state | |
| HRESULT CVideoTransformFilter::StartStreaming() | |
| { | |
| m_itrLate = 0; | |
| m_nKeyFramePeriod = 0; // No QM until we see at least 2 key frames | |
| m_nFramesSinceKeyFrame = 0; | |
| m_bSkipping = FALSE; | |
| m_tDecodeStart = 0; | |
| m_itrAvgDecode = 300000; // 30mSec - probably allows skipping | |
| m_bQualityChanged = FALSE; | |
| m_bSampleSkipped = FALSE; | |
| return NOERROR; | |
| } | |
| // Overriden to reset quality management information | |
| HRESULT CVideoTransformFilter::EndFlush() | |
| { | |
| { | |
| // Synchronize | |
| CAutoLock lck(&m_csReceive); | |
| // Reset our stats | |
| // | |
| // Note - we don't want to call derived classes here, | |
| // we only want to reset our internal variables and this | |
| // is a convenient way to do it | |
| CVideoTransformFilter::StartStreaming(); | |
| } | |
| return CTransformFilter::EndFlush(); | |
| } | |
| HRESULT CVideoTransformFilter::AbortPlayback(HRESULT hr) | |
| { | |
| NotifyEvent(EC_ERRORABORT, hr, 0); | |
| m_pOutput->DeliverEndOfStream(); | |
| return hr; | |
| } | |
| // Receive() | |
| // | |
| // Accept a sample from upstream, decide whether to process it | |
| // or drop it. If we process it then get a buffer from the | |
| // allocator of the downstream connection, transform it into the | |
| // new buffer and deliver it to the downstream filter. | |
| // If we decide not to process it then we do not get a buffer. | |
| // Remember that although this code will notice format changes coming into | |
| // the input pin, it will NOT change its output format if that results | |
| // in the filter needing to make a corresponding output format change. Your | |
| // derived filter will have to take care of that. (eg. a palette change if | |
| // the input and output is an 8 bit format). If the input sample is discarded | |
| // and nothing is sent out for this Receive, please remember to put the format | |
| // change on the first output sample that you actually do send. | |
| // If your filter will produce the same output type even when the input type | |
| // changes, then this base class code will do everything you need. | |
| HRESULT CVideoTransformFilter::Receive(IMediaSample *pSample) | |
| { | |
| // If the next filter downstream is the video renderer, then it may | |
| // be able to operate in DirectDraw mode which saves copying the data | |
| // and gives higher performance. In that case the buffer which we | |
| // get from GetDeliveryBuffer will be a DirectDraw buffer, and | |
| // drawing into this buffer draws directly onto the display surface. | |
| // This means that any waiting for the correct time to draw occurs | |
| // during GetDeliveryBuffer, and that once the buffer is given to us | |
| // the video renderer will count it in its statistics as a frame drawn. | |
| // This means that any decision to drop the frame must be taken before | |
| // calling GetDeliveryBuffer. | |
| ASSERT(CritCheckIn(&m_csReceive)); | |
| AM_MEDIA_TYPE *pmtOut, *pmt; | |
| #ifdef DEBUG | |
| FOURCCMap fccOut; | |
| #endif | |
| HRESULT hr; | |
| ASSERT(pSample); | |
| IMediaSample * pOutSample; | |
| // If no output pin to deliver to then no point sending us data | |
| ASSERT (m_pOutput != NULL) ; | |
| // The source filter may dynamically ask us to start transforming from a | |
| // different media type than the one we're using now. If we don't, we'll | |
| // draw garbage. (typically, this is a palette change in the movie, | |
| // but could be something more sinister like the compression type changing, | |
| // or even the video size changing) | |
| #define rcS1 ((VIDEOINFOHEADER *)(pmt->pbFormat))->rcSource | |
| #define rcT1 ((VIDEOINFOHEADER *)(pmt->pbFormat))->rcTarget | |
| pSample->GetMediaType(&pmt); | |
| if (pmt != NULL && pmt->pbFormat != NULL) { | |
| // spew some debug output | |
| ASSERT(!IsEqualGUID(pmt->majortype, GUID_NULL)); | |
| #ifdef DEBUG | |
| fccOut.SetFOURCC(&pmt->subtype); | |
| LONG lCompression = HEADER(pmt->pbFormat)->biCompression; | |
| LONG lBitCount = HEADER(pmt->pbFormat)->biBitCount; | |
| LONG lStride = (HEADER(pmt->pbFormat)->biWidth * lBitCount + 7) / 8; | |
| lStride = (lStride + 3) & ~3; | |
| DbgLog((LOG_TRACE,3,TEXT("*Changing input type on the fly to"))); | |
| DbgLog((LOG_TRACE,3,TEXT("FourCC: %lx Compression: %lx BitCount: %ld"), | |
| fccOut.GetFOURCC(), lCompression, lBitCount)); | |
| DbgLog((LOG_TRACE,3,TEXT("biHeight: %ld rcDst: (%ld, %ld, %ld, %ld)"), | |
| HEADER(pmt->pbFormat)->biHeight, | |
| rcT1.left, rcT1.top, rcT1.right, rcT1.bottom)); | |
| DbgLog((LOG_TRACE,3,TEXT("rcSrc: (%ld, %ld, %ld, %ld) Stride: %ld"), | |
| rcS1.left, rcS1.top, rcS1.right, rcS1.bottom, | |
| lStride)); | |
| #endif | |
| // now switch to using the new format. I am assuming that the | |
| // derived filter will do the right thing when its media type is | |
| // switched and streaming is restarted. | |
| StopStreaming(); | |
| m_pInput->CurrentMediaType() = *pmt; | |
| DeleteMediaType(pmt); | |
| // if this fails, playback will stop, so signal an error | |
| hr = StartStreaming(); | |
| if (FAILED(hr)) { | |
| return AbortPlayback(hr); | |
| } | |
| } | |
| // Now that we have noticed any format changes on the input sample, it's | |
| // OK to discard it. | |
| if (ShouldSkipFrame(pSample)) { | |
| MSR_NOTE(m_idSkip); | |
| m_bSampleSkipped = TRUE; | |
| return NOERROR; | |
| } | |
| // Set up the output sample | |
| hr = InitializeOutputSample(pSample, &pOutSample); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| m_bSampleSkipped = FALSE; | |
| // The renderer may ask us to on-the-fly to start transforming to a | |
| // different format. If we don't obey it, we'll draw garbage | |
| #define rcS ((VIDEOINFOHEADER *)(pmtOut->pbFormat))->rcSource | |
| #define rcT ((VIDEOINFOHEADER *)(pmtOut->pbFormat))->rcTarget | |
| pOutSample->GetMediaType(&pmtOut); | |
| if (pmtOut != NULL && pmtOut->pbFormat != NULL) { | |
| // spew some debug output | |
| ASSERT(!IsEqualGUID(pmtOut->majortype, GUID_NULL)); | |
| #ifdef DEBUG | |
| fccOut.SetFOURCC(&pmtOut->subtype); | |
| LONG lCompression = HEADER(pmtOut->pbFormat)->biCompression; | |
| LONG lBitCount = HEADER(pmtOut->pbFormat)->biBitCount; | |
| LONG lStride = (HEADER(pmtOut->pbFormat)->biWidth * lBitCount + 7) / 8; | |
| lStride = (lStride + 3) & ~3; | |
| DbgLog((LOG_TRACE,3,TEXT("*Changing output type on the fly to"))); | |
| DbgLog((LOG_TRACE,3,TEXT("FourCC: %lx Compression: %lx BitCount: %ld"), | |
| fccOut.GetFOURCC(), lCompression, lBitCount)); | |
| DbgLog((LOG_TRACE,3,TEXT("biHeight: %ld rcDst: (%ld, %ld, %ld, %ld)"), | |
| HEADER(pmtOut->pbFormat)->biHeight, | |
| rcT.left, rcT.top, rcT.right, rcT.bottom)); | |
| DbgLog((LOG_TRACE,3,TEXT("rcSrc: (%ld, %ld, %ld, %ld) Stride: %ld"), | |
| rcS.left, rcS.top, rcS.right, rcS.bottom, | |
| lStride)); | |
| #endif | |
| // now switch to using the new format. I am assuming that the | |
| // derived filter will do the right thing when its media type is | |
| // switched and streaming is restarted. | |
| StopStreaming(); | |
| m_pOutput->CurrentMediaType() = *pmtOut; | |
| DeleteMediaType(pmtOut); | |
| hr = StartStreaming(); | |
| if (SUCCEEDED(hr)) { | |
| // a new format, means a new empty buffer, so wait for a keyframe | |
| // before passing anything on to the renderer. | |
| // !!! a keyframe may never come, so give up after 30 frames | |
| DbgLog((LOG_TRACE,3,TEXT("Output format change means we must wait for a keyframe"))); | |
| m_nWaitForKey = 30; | |
| // if this fails, playback will stop, so signal an error | |
| } else { | |
| // Must release the sample before calling AbortPlayback | |
| // because we might be holding the win16 lock or | |
| // ddraw lock | |
| pOutSample->Release(); | |
| AbortPlayback(hr); | |
| return hr; | |
| } | |
| } | |
| // After a discontinuity, we need to wait for the next key frame | |
| if (pSample->IsDiscontinuity() == S_OK) { | |
| DbgLog((LOG_TRACE,3,TEXT("Non-key discontinuity - wait for keyframe"))); | |
| m_nWaitForKey = 30; | |
| } | |
| // Start timing the transform (and log it if PERF is defined) | |
| if (SUCCEEDED(hr)) { | |
| m_tDecodeStart = timeGetTime(); | |
| MSR_START(m_idTransform); | |
| // have the derived class transform the data | |
| hr = Transform(pSample, pOutSample); | |
| // Stop the clock (and log it if PERF is defined) | |
| MSR_STOP(m_idTransform); | |
| m_tDecodeStart = timeGetTime()-m_tDecodeStart; | |
| m_itrAvgDecode = m_tDecodeStart*(10000/16) + 15*(m_itrAvgDecode/16); | |
| // Maybe we're waiting for a keyframe still? | |
| if (m_nWaitForKey) | |
| m_nWaitForKey--; | |
| if (m_nWaitForKey && pSample->IsSyncPoint() == S_OK) | |
| m_nWaitForKey = FALSE; | |
| // if so, then we don't want to pass this on to the renderer | |
| if (m_nWaitForKey && hr == NOERROR) { | |
| DbgLog((LOG_TRACE,3,TEXT("still waiting for a keyframe"))); | |
| hr = S_FALSE; | |
| } | |
| } | |
| if (FAILED(hr)) { | |
| DbgLog((LOG_TRACE,1,TEXT("Error from video transform"))); | |
| } else { | |
| // the Transform() function can return S_FALSE to indicate that the | |
| // sample should not be delivered; we only deliver the sample if it's | |
| // really S_OK (same as NOERROR, of course.) | |
| // Try not to return S_FALSE to a direct draw buffer (it's wasteful) | |
| // Try to take the decision earlier - before you get it. | |
| if (hr == NOERROR) { | |
| hr = m_pOutput->Deliver(pOutSample); | |
| } else { | |
| // S_FALSE returned from Transform is a PRIVATE agreement | |
| // We should return NOERROR from Receive() in this case because returning S_FALSE | |
| // from Receive() means that this is the end of the stream and no more data should | |
| // be sent. | |
| if (S_FALSE == hr) { | |
| // We must Release() the sample before doing anything | |
| // like calling the filter graph because having the | |
| // sample means we may have the DirectDraw lock | |
| // (== win16 lock on some versions) | |
| pOutSample->Release(); | |
| m_bSampleSkipped = TRUE; | |
| if (!m_bQualityChanged) { | |
| m_bQualityChanged = TRUE; | |
| NotifyEvent(EC_QUALITY_CHANGE,0,0); | |
| } | |
| return NOERROR; | |
| } | |
| } | |
| } | |
| // release the output buffer. If the connected pin still needs it, | |
| // it will have addrefed it itself. | |
| pOutSample->Release(); | |
| ASSERT(CritCheckIn(&m_csReceive)); | |
| return hr; | |
| } | |
| BOOL CVideoTransformFilter::ShouldSkipFrame( IMediaSample * pIn) | |
| { | |
| REFERENCE_TIME trStart, trStopAt; | |
| HRESULT hr = pIn->GetTime(&trStart, &trStopAt); | |
| // Don't skip frames with no timestamps | |
| if (hr != S_OK) | |
| return FALSE; | |
| int itrFrame = (int)(trStopAt - trStart); // frame duration | |
| if(S_OK==pIn->IsSyncPoint()) { | |
| MSR_INTEGER(m_idFrameType, 1); | |
| if ( m_nKeyFramePeriod < m_nFramesSinceKeyFrame ) { | |
| // record the max | |
| m_nKeyFramePeriod = m_nFramesSinceKeyFrame; | |
| } | |
| m_nFramesSinceKeyFrame = 0; | |
| m_bSkipping = FALSE; | |
| } else { | |
| MSR_INTEGER(m_idFrameType, 2); | |
| if ( m_nFramesSinceKeyFrame>m_nKeyFramePeriod | |
| && m_nKeyFramePeriod>0 | |
| ) { | |
| // We haven't seen the key frame yet, but we were clearly being | |
| // overoptimistic about how frequent they are. | |
| m_nKeyFramePeriod = m_nFramesSinceKeyFrame; | |
| } | |
| } | |
| // Whatever we might otherwise decide, | |
| // if we are taking only a small fraction of the required frame time to decode | |
| // then any quality problems are actually coming from somewhere else. | |
| // Could be a net problem at the source for instance. In this case there's | |
| // no point in us skipping frames here. | |
| if (m_itrAvgDecode*4>itrFrame) { | |
| // Don't skip unless we are at least a whole frame late. | |
| // (We would skip B frames if more than 1/2 frame late, but they're safe). | |
| if ( m_itrLate > itrFrame ) { | |
| // Don't skip unless the anticipated key frame would be no more than | |
| // 1 frame early. If the renderer has not been waiting (we *guess* | |
| // it hasn't because we're late) then it will allow frames to be | |
| // played early by up to a frame. | |
| // Let T = Stream time from now to anticipated next key frame | |
| // = (frame duration) * (KeyFramePeriod - FramesSinceKeyFrame) | |
| // So we skip if T - Late < one frame i.e. | |
| // (duration) * (freq - FramesSince) - Late < duration | |
| // or (duration) * (freq - FramesSince - 1) < Late | |
| // We don't dare skip until we have seen some key frames and have | |
| // some idea how often they occur and they are reasonably frequent. | |
| if (m_nKeyFramePeriod>0) { | |
| // It would be crazy - but we could have a stream with key frames | |
| // a very long way apart - and if they are further than about | |
| // 3.5 minutes apart then we could get arithmetic overflow in | |
| // reference time units. Therefore we switch to mSec at this point | |
| int it = (itrFrame/10000) | |
| * (m_nKeyFramePeriod-m_nFramesSinceKeyFrame - 1); | |
| MSR_INTEGER(m_idTimeTillKey, it); | |
| // For debug - might want to see the details - dump them as scratch pad | |
| #ifdef VTRANSPERF | |
| MSR_INTEGER(0, itrFrame); | |
| MSR_INTEGER(0, m_nFramesSinceKeyFrame); | |
| MSR_INTEGER(0, m_nKeyFramePeriod); | |
| #endif | |
| if (m_itrLate/10000 > it) { | |
| m_bSkipping = TRUE; | |
| // Now we are committed. Once we start skipping, we | |
| // cannot stop until we hit a key frame. | |
| } else { | |
| #ifdef VTRANSPERF | |
| MSR_INTEGER(0, 777770); // not near enough to next key | |
| #endif | |
| } | |
| } else { | |
| #ifdef VTRANSPERF | |
| MSR_INTEGER(0, 777771); // Next key not predictable | |
| #endif | |
| } | |
| } else { | |
| #ifdef VTRANSPERF | |
| MSR_INTEGER(0, 777772); // Less than one frame late | |
| MSR_INTEGER(0, m_itrLate); | |
| MSR_INTEGER(0, itrFrame); | |
| #endif | |
| } | |
| } else { | |
| #ifdef VTRANSPERF | |
| MSR_INTEGER(0, 777773); // Decode time short - not not worth skipping | |
| MSR_INTEGER(0, m_itrAvgDecode); | |
| MSR_INTEGER(0, itrFrame); | |
| #endif | |
| } | |
| ++m_nFramesSinceKeyFrame; | |
| if (m_bSkipping) { | |
| // We will count down the lateness as we skip each frame. | |
| // We re-assess each frame. The key frame might not arrive when expected. | |
| // We reset m_itrLate if we get a new Quality message, but actually that's | |
| // not likely because we're not sending frames on to the Renderer. In | |
| // fact if we DID get another one it would mean that there's a long | |
| // pipe between us and the renderer and we might need an altogether | |
| // better strategy to avoid hunting! | |
| m_itrLate = m_itrLate - itrFrame; | |
| } | |
| MSR_INTEGER(m_idLate, (int)m_itrLate/10000 ); // Note how late we think we are | |
| if (m_bSkipping) { | |
| if (!m_bQualityChanged) { | |
| m_bQualityChanged = TRUE; | |
| NotifyEvent(EC_QUALITY_CHANGE,0,0); | |
| } | |
| } | |
| return m_bSkipping; | |
| } | |
| HRESULT CVideoTransformFilter::AlterQuality(Quality q) | |
| { | |
| // to reduce the amount of 64 bit arithmetic, m_itrLate is an int. | |
| // +, -, >, == etc are not too bad, but * and / are painful. | |
| if (m_itrLate>300000000) { | |
| // Avoid overflow and silliness - more than 30 secs late is already silly | |
| m_itrLate = 300000000; | |
| } else { | |
| m_itrLate = (int)q.Late; | |
| } | |
| // We ignore the other fields | |
| // We're actually not very good at handling this. In non-direct draw mode | |
| // most of the time can be spent in the renderer which can skip any frame. | |
| // In that case we'd rather the renderer handled things. | |
| // Nevertheless we will keep an eye on it and if we really start getting | |
| // a very long way behind then we will actually skip - but we'll still tell | |
| // the renderer (or whoever is downstream) that they should handle quality. | |
| return E_FAIL; // Tell the renderer to do his thing. | |
| } | |
| // This will avoid several hundred useless warnings if compiled -W4 by MS VC++ v4 | |
| #pragma warning(disable:4514) | |