topical media & game development

talk show tell print

hush-src-multi-BaseClasses-vtrans.cpp / cpp



  //------------------------------------------------------------------------------
  // File: Vtrans.cpp
  //
  // Desc: DirectShow base classes.
  //
  // Copyright (c) Microsoft Corporation.  All rights reserved.
  //------------------------------------------------------------------------------
  
  include <streams.h>
  include <measure.h>
  // #include <vtransfr.h>         // now in precomp file streams.h
  
  CVideoTransformFilter::CVideoTransformFilter
      ( TCHAR *pName, 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)
  
  


(C) Æliens 20/2/2008

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.