mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-17 22:06:41 +00:00
42bdeaf52c
Original repo is here: https://github.com/microsoft/Windows-classic-samples Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1577>
468 lines
17 KiB
C++
468 lines
17 KiB
C++
//------------------------------------------------------------------------------
|
|
// 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)
|
|
|