123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- //------------------------------------------------------------------------------
- // 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)
|