/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "videosurfacefilter.h" #include "directshoweventloop.h" #include "directshowglobal.h" #include "directshowpinenum.h" #include "mediasamplevideobuffer.h" #include #include #include #include #include // { e23cad72-153d-406c-bf3f-4c4b523d96f2 } DEFINE_GUID(CLSID_VideoSurfaceFilter, 0xe23cad72, 0x153d, 0x406c, 0xbf, 0x3f, 0x4c, 0x4b, 0x52, 0x3d, 0x96, 0xf2); VideoSurfaceFilter::VideoSurfaceFilter( QAbstractVideoSurface *surface, DirectShowEventLoop *loop, QObject *parent) : QObject(parent) , m_ref(1) , m_state(State_Stopped) , m_surface(surface) , m_loop(loop) , m_graph(0) , m_peerPin(0) , m_bytesPerLine(0) , m_startResult(S_OK) , m_pinId(QString::fromLatin1("reference")) , m_sampleScheduler(static_cast(this)) { connect(surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged())); connect(&m_sampleScheduler, SIGNAL(sampleReady()), this, SLOT(sampleReady())); } VideoSurfaceFilter::~VideoSurfaceFilter() { Q_ASSERT(m_ref == 1); } HRESULT VideoSurfaceFilter::QueryInterface(REFIID riid, void **ppvObject) { // 2dd74950-a890-11d1-abe8-00a0c905f375 static const GUID iid_IAmFilterMiscFlags = { 0x2dd74950, 0xa890, 0x11d1, {0xab, 0xe8, 0x00, 0xa0, 0xc9, 0x05, 0xf3, 0x75} }; if (!ppvObject) { return E_POINTER; } else if (riid == IID_IUnknown || riid == IID_IPersist || riid == IID_IMediaFilter || riid == IID_IBaseFilter) { *ppvObject = static_cast(this); } else if (riid == iid_IAmFilterMiscFlags) { *ppvObject = static_cast(this); } else if (riid == IID_IPin) { *ppvObject = static_cast(this); } else if (riid == IID_IMemInputPin) { *ppvObject = static_cast(&m_sampleScheduler); } else { *ppvObject = 0; return E_NOINTERFACE; } AddRef(); return S_OK; } ULONG VideoSurfaceFilter::AddRef() { return InterlockedIncrement(&m_ref); } ULONG VideoSurfaceFilter::Release() { ULONG ref = InterlockedDecrement(&m_ref); Q_ASSERT(ref != 0); return ref; } HRESULT VideoSurfaceFilter::GetClassID(CLSID *pClassID) { *pClassID = CLSID_VideoSurfaceFilter; return S_OK; } HRESULT VideoSurfaceFilter::Run(REFERENCE_TIME tStart) { m_state = State_Running; m_sampleScheduler.run(tStart); return S_OK; } HRESULT VideoSurfaceFilter::Pause() { m_state = State_Paused; m_sampleScheduler.pause(); return S_OK; } HRESULT VideoSurfaceFilter::Stop() { m_state = State_Stopped; m_sampleScheduler.stop(); return S_OK; } HRESULT VideoSurfaceFilter::GetState(DWORD dwMilliSecsTimeout, FILTER_STATE *pState) { Q_UNUSED(dwMilliSecsTimeout) if (!pState) return E_POINTER; *pState = m_state; return S_OK; } HRESULT VideoSurfaceFilter::SetSyncSource(IReferenceClock *pClock) { m_sampleScheduler.setClock(pClock); return S_OK; } HRESULT VideoSurfaceFilter::GetSyncSource(IReferenceClock **ppClock) { if (!ppClock) { return E_POINTER; } else { *ppClock = m_sampleScheduler.clock(); if (*ppClock) { (*ppClock)->AddRef(); return S_OK; } else { return S_FALSE; } } } HRESULT VideoSurfaceFilter::EnumPins(IEnumPins **ppEnum) { if (ppEnum) { *ppEnum = new DirectShowPinEnum(QList() << this); return S_OK; } else { return E_POINTER; } } HRESULT VideoSurfaceFilter::FindPin(LPCWSTR pId, IPin **ppPin) { if (!ppPin || !pId) { return E_POINTER; } else if (QString::fromWCharArray(pId) == m_pinId) { AddRef(); *ppPin = this; return S_OK; } else { return VFW_E_NOT_FOUND; } } HRESULT VideoSurfaceFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName) { m_graph = pGraph; m_name = QString::fromWCharArray(pName); return S_OK; } HRESULT VideoSurfaceFilter::QueryFilterInfo(FILTER_INFO *pInfo) { if (pInfo) { QString name = m_name; if (name.length() >= MAX_FILTER_NAME) name.truncate(MAX_FILTER_NAME - 1); int length = name.toWCharArray(pInfo->achName); pInfo->achName[length] = '\0'; if (m_graph) m_graph->AddRef(); pInfo->pGraph = m_graph; return S_OK; } else { return E_POINTER; } } HRESULT VideoSurfaceFilter::QueryVendorInfo(LPWSTR *pVendorInfo) { Q_UNUSED(pVendorInfo); return E_NOTIMPL; } ULONG VideoSurfaceFilter::GetMiscFlags() { return AM_FILTER_MISC_FLAGS_IS_RENDERER; } HRESULT VideoSurfaceFilter::Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) { Q_UNUSED(pReceivePin) Q_UNUSED(pmt) // This is an input pin, you shouldn't be calling Connect on it. return E_POINTER; } HRESULT VideoSurfaceFilter::ReceiveConnection(IPin *pConnector, const AM_MEDIA_TYPE *pmt) { if (!pConnector) { return E_POINTER; } else if (!pmt) { return E_POINTER; } else { HRESULT hr; QMutexLocker locker(&m_mutex); if (m_peerPin) { hr = VFW_E_ALREADY_CONNECTED; } else if (pmt->majortype != MEDIATYPE_Video) { hr = VFW_E_TYPE_NOT_ACCEPTED; } else { m_surfaceFormat = DirectShowMediaType::formatFromType(*pmt); m_bytesPerLine = DirectShowMediaType::bytesPerLine(m_surfaceFormat); if (thread() == QThread::currentThread()) { hr = start(); } else { m_loop->postEvent(this, new QEvent(QEvent::Type(StartSurface))); m_wait.wait(&m_mutex); hr = m_startResult; } } if (hr == S_OK) { m_peerPin = pConnector; m_peerPin->AddRef(); DirectShowMediaType::copy(&m_mediaType, *pmt); } return hr; } } HRESULT VideoSurfaceFilter::start() { if (!m_surface->isFormatSupported(m_surfaceFormat)) { return VFW_E_TYPE_NOT_ACCEPTED; } if (!m_surface->start(m_surfaceFormat)) { return VFW_E_TYPE_NOT_ACCEPTED; } else { return S_OK; } } HRESULT VideoSurfaceFilter::Disconnect() { QMutexLocker locker(&m_mutex); if (!m_peerPin) return S_FALSE; if (thread() == QThread::currentThread()) { stop(); } else { m_loop->postEvent(this, new QEvent(QEvent::Type(StopSurface))); m_wait.wait(&m_mutex); } m_mediaType.clear(); m_sampleScheduler.NotifyAllocator(0, FALSE); m_peerPin->Release(); m_peerPin = 0; return S_OK; } void VideoSurfaceFilter::stop() { m_surface->stop(); } HRESULT VideoSurfaceFilter::ConnectedTo(IPin **ppPin) { if (!ppPin) { return E_POINTER; } else { QMutexLocker locker(&m_mutex); if (!m_peerPin) { return VFW_E_NOT_CONNECTED; } else { m_peerPin->AddRef(); *ppPin = m_peerPin; return S_OK; } } } HRESULT VideoSurfaceFilter::ConnectionMediaType(AM_MEDIA_TYPE *pmt) { if (!pmt) { return E_POINTER; } else { QMutexLocker locker(&m_mutex); if (!m_peerPin) { return VFW_E_NOT_CONNECTED; } else { DirectShowMediaType::copy(pmt, m_mediaType); return S_OK; } } } HRESULT VideoSurfaceFilter::QueryPinInfo(PIN_INFO *pInfo) { if (!pInfo) { return E_POINTER; } else { AddRef(); pInfo->pFilter = this; pInfo->dir = PINDIR_INPUT; const int bytes = qMin(MAX_FILTER_NAME, (m_pinId.length() + 1) * 2); ::memcpy(pInfo->achName, m_pinId.utf16(), bytes); return S_OK; } } HRESULT VideoSurfaceFilter::QueryId(LPWSTR *Id) { if (!Id) { return E_POINTER; } else { const int bytes = (m_pinId.length() + 1) * 2; *Id = static_cast(::CoTaskMemAlloc(bytes)); ::memcpy(*Id, m_pinId.utf16(), bytes); return S_OK; } } HRESULT VideoSurfaceFilter::QueryAccept(const AM_MEDIA_TYPE *pmt) { return !m_surface->isFormatSupported(DirectShowMediaType::formatFromType(*pmt)) ? S_OK : S_FALSE; } HRESULT VideoSurfaceFilter::EnumMediaTypes(IEnumMediaTypes **ppEnum) { if (!ppEnum) { return E_POINTER; } else { QMutexLocker locker(&m_mutex); *ppEnum = createMediaTypeEnum(); return S_OK; } } HRESULT VideoSurfaceFilter::QueryInternalConnections(IPin **apPin, ULONG *nPin) { Q_UNUSED(apPin); Q_UNUSED(nPin); return E_NOTIMPL; } HRESULT VideoSurfaceFilter::EndOfStream() { QMutexLocker locker(&m_mutex); if (!m_sampleScheduler.scheduleEndOfStream()) { if (IMediaEventSink *sink = com_cast(m_graph, IID_IMediaEventSink)) { sink->Notify( EC_COMPLETE, S_OK, reinterpret_cast(static_cast(this))); sink->Release(); } } return S_OK; } HRESULT VideoSurfaceFilter::BeginFlush() { QMutexLocker locker(&m_mutex); m_sampleScheduler.setFlushing(true); if (thread() == QThread::currentThread()) { flush(); } else { m_loop->postEvent(this, new QEvent(QEvent::Type(FlushSurface))); m_wait.wait(&m_mutex); } return S_OK; } HRESULT VideoSurfaceFilter::EndFlush() { QMutexLocker locker(&m_mutex); m_sampleScheduler.setFlushing(false); return S_OK; } void VideoSurfaceFilter::flush() { m_surface->present(QVideoFrame()); m_wait.wakeAll(); } HRESULT VideoSurfaceFilter::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) { Q_UNUSED(tStart); Q_UNUSED(tStop); Q_UNUSED(dRate); return S_OK; } HRESULT VideoSurfaceFilter::QueryDirection(PIN_DIRECTION *pPinDir) { if (!pPinDir) { return E_POINTER; } else { *pPinDir = PINDIR_INPUT; return S_OK; } } int VideoSurfaceFilter::currentMediaTypeToken() { QMutexLocker locker(&m_mutex); return DirectShowMediaTypeList::currentMediaTypeToken(); } HRESULT VideoSurfaceFilter::nextMediaType( int token, int *index, ULONG count, AM_MEDIA_TYPE **types, ULONG *fetchedCount) { QMutexLocker locker(&m_mutex); return DirectShowMediaTypeList::nextMediaType(token, index, count, types, fetchedCount); } HRESULT VideoSurfaceFilter::skipMediaType(int token, int *index, ULONG count) { QMutexLocker locker(&m_mutex); return DirectShowMediaTypeList::skipMediaType(token, index, count); } HRESULT VideoSurfaceFilter::cloneMediaType(int token, int index, IEnumMediaTypes **enumeration) { QMutexLocker locker(&m_mutex); return DirectShowMediaTypeList::cloneMediaType(token, index, enumeration); } void VideoSurfaceFilter::customEvent(QEvent *event) { const int type = event->type(); if (type == StartSurface) { QMutexLocker locker(&m_mutex); m_startResult = start(); m_wait.wakeAll(); } else if (type == StopSurface) { QMutexLocker locker(&m_mutex); stop(); m_wait.wakeAll(); } else if (type == FlushSurface) { QMutexLocker locker(&m_mutex); flush(); m_wait.wakeAll(); } else { QObject::customEvent(event); } } void VideoSurfaceFilter::supportedFormatsChanged() { QMutexLocker locker(&m_mutex); // MEDIASUBTYPE_None; static const GUID none = { 0xe436eb8e, 0x524f, 0x11ce, {0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} }; QList formats = m_surface->supportedPixelFormats(); QVector mediaTypes; mediaTypes.reserve(formats.count()); AM_MEDIA_TYPE type; type.majortype = MEDIATYPE_Video; type.bFixedSizeSamples = TRUE; type.bTemporalCompression = FALSE; type.lSampleSize = 0; type.formattype = GUID_NULL; type.pUnk = 0; type.cbFormat = 0; type.pbFormat = 0; foreach (QVideoFrame::PixelFormat format, formats) { type.subtype = DirectShowMediaType::convertPixelFormat(format); if (type.subtype != none) mediaTypes.append(type); } setMediaTypes(mediaTypes); } void VideoSurfaceFilter::sampleReady() { bool eos = false; IMediaSample *sample = m_sampleScheduler.takeSample(&eos); if (sample) { m_surface->present(QVideoFrame( new MediaSampleVideoBuffer(sample, m_bytesPerLine), m_surfaceFormat.frameSize(), m_surfaceFormat.pixelFormat())); sample->Release(); if (eos) { if (IMediaEventSink *sink = com_cast(m_graph, IID_IMediaEventSink)) { sink->Notify( EC_COMPLETE, S_OK, reinterpret_cast(static_cast(this))); sink->Release(); } } } }