blob: 3cc42dc21ea4d7575ba822cfbd06ff1f1fb867e2 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <dshow.h>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#include "directshowplayerservice.h"
#include "directshowaudioendpointcontrol.h"
#include "directshowmetadatacontrol.h"
#include "vmr9videowindowcontrol.h"
#include "directshowiosource.h"
#include "directshowplayercontrol.h"
#include "directshowvideorenderercontrol.h"
#include "directshowutils.h"
#include "directshowglobal.h"
#include "directshowaudioprobecontrol.h"
#include "directshowvideoprobecontrol.h"
#include "directshowsamplegrabber.h"
#if QT_CONFIG(evr)
#include "directshowevrvideowindowcontrol.h"
#else
#include <mmreg.h>
#endif
#include "qmediacontent.h"
#include <QtMultimedia/private/qtmultimedia-config_p.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qdir.h>
#include <QtCore/qthread.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qsize.h>
#include <QtMultimedia/qaudiobuffer.h>
#include <QtMultimedia/qvideoframe.h>
#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
#if QT_CONFIG(wmsdk)
# include <wmsdk.h>
#endif
#ifndef Q_CC_MINGW
# include <comdef.h>
#endif
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(DirectShowEventLoop, qt_directShowEventLoop)
static QString comError(HRESULT hr)
{
#ifndef Q_CC_MINGW // MinGW 5.3 no longer has swprintf_s().
_com_error error(hr);
return QString::fromWCharArray(error.ErrorMessage());
#else
Q_UNUSED(hr)
return QString();
#endif
}
// QMediaPlayer uses millisecond time units, direct show uses 100 nanosecond units.
static const int qt_directShowTimeScale = 10000;
class DirectShowPlayerServiceThread : public QThread
{
public:
DirectShowPlayerServiceThread(DirectShowPlayerService *service)
: m_service(service)
{
}
protected:
void run() override { m_service->run(); }
private:
DirectShowPlayerService *m_service;
};
DirectShowPlayerService::DirectShowPlayerService(QObject *parent)
: QMediaService(parent)
, m_loop(qt_directShowEventLoop())
, m_taskHandle(::CreateEvent(nullptr, FALSE, FALSE, nullptr))
{
m_playerControl = new DirectShowPlayerControl(this);
m_metaDataControl = new DirectShowMetaDataControl(this);
m_audioEndpointControl = new DirectShowAudioEndpointControl(this);
m_taskThread = new DirectShowPlayerServiceThread(this);
m_taskThread->start();
}
DirectShowPlayerService::~DirectShowPlayerService()
{
{
QMutexLocker locker(&m_mutex);
releaseGraph();
m_pendingTasks = Shutdown;
::SetEvent(m_taskHandle);
}
m_taskThread->wait();
delete m_taskThread;
if (m_audioOutput) {
m_audioOutput->Release();
m_audioOutput = nullptr;
}
if (m_videoOutput) {
m_videoOutput->Release();
m_videoOutput = nullptr;
}
delete m_playerControl;
delete m_audioEndpointControl;
delete m_metaDataControl;
delete m_videoRendererControl;
delete m_videoWindowControl;
delete m_audioProbeControl;
delete m_videoProbeControl;
::CloseHandle(m_taskHandle);
}
QMediaControl *DirectShowPlayerService::requestControl(const char *name)
{
if (qstrcmp(name, QMediaPlayerControl_iid) == 0)
return m_playerControl;
if (qstrcmp(name, QAudioOutputSelectorControl_iid) == 0)
return m_audioEndpointControl;
if (qstrcmp(name, QMetaDataReaderControl_iid) == 0)
return m_metaDataControl;
if (qstrcmp(name, QVideoRendererControl_iid) == 0) {
if (!m_videoRendererControl && !m_videoWindowControl) {
m_videoRendererControl = new DirectShowVideoRendererControl(m_loop);
connect(m_videoRendererControl, &DirectShowVideoRendererControl::filterChanged,
this, &DirectShowPlayerService::videoOutputChanged);
return m_videoRendererControl;
}
return nullptr;
}
if (qstrcmp(name, QVideoWindowControl_iid) == 0) {
if (!m_videoRendererControl && !m_videoWindowControl) {
IBaseFilter *filter{};
#if QT_CONFIG(evr)
if (!qgetenv("QT_DIRECTSHOW_NO_EVR").toInt()) {
DirectShowEvrVideoWindowControl *evrControl = new DirectShowEvrVideoWindowControl;
if ((filter = evrControl->filter()))
m_videoWindowControl = evrControl;
else
delete evrControl;
}
#endif
// Fall back to the VMR9 if the EVR is not available
if (!m_videoWindowControl) {
Vmr9VideoWindowControl *vmr9Control = new Vmr9VideoWindowControl;
filter = vmr9Control->filter();
m_videoWindowControl = vmr9Control;
}
setVideoOutput(filter);
return m_videoWindowControl;
}
return nullptr;
}
if (qstrcmp(name, QMediaAudioProbeControl_iid) == 0) {
if (!m_audioProbeControl)
m_audioProbeControl = new DirectShowAudioProbeControl();
m_audioProbeControl->ref();
updateAudioProbe();
return m_audioProbeControl;
}
if (qstrcmp(name, QMediaVideoProbeControl_iid) == 0) {
if (!m_videoProbeControl)
m_videoProbeControl = new DirectShowVideoProbeControl();
m_videoProbeControl->ref();
updateVideoProbe();
return m_videoProbeControl;
}
return nullptr;
}
void DirectShowPlayerService::releaseControl(QMediaControl *control)
{
if (!control) {
qWarning("QMediaService::releaseControl():"
" Attempted release of null control");
} else if (control == m_videoRendererControl) {
setVideoOutput(nullptr);
delete m_videoRendererControl;
m_videoRendererControl = nullptr;
} else if (control == m_videoWindowControl) {
setVideoOutput(nullptr);
delete m_videoWindowControl;
m_videoWindowControl = nullptr;
} else if (control == m_audioProbeControl) {
if (!m_audioProbeControl->deref()) {
DirectShowAudioProbeControl *old = m_audioProbeControl;
m_audioProbeControl = nullptr;
updateAudioProbe();
delete old;
}
} else if (control == m_videoProbeControl) {
if (!m_videoProbeControl->deref()) {
DirectShowVideoProbeControl *old = m_videoProbeControl;
m_videoProbeControl = nullptr;
updateVideoProbe();
delete old;
}
}
}
void DirectShowPlayerService::load(const QMediaContent &media, QIODevice *stream)
{
QMutexLocker locker(&m_mutex);
m_pendingTasks = 0;
if (m_graph)
releaseGraph();
m_url = media.request().url();
m_stream = stream;
m_error = QMediaPlayer::NoError;
m_errorString = QString();
m_position = 0;
m_seekPosition = -1;
m_duration = 0;
m_streamTypes = 0;
m_executedTasks = 0;
m_buffering = false;
m_seekable = false;
m_atEnd = false;
m_dontCacheNextSeekResult = false;
m_metaDataControl->setMetadata(QVariantMap());
if (m_url.isEmpty() && !stream) {
m_pendingTasks = 0;
m_graphStatus = NoMedia;
} else if (stream && (!stream->isReadable() || stream->isSequential())) {
m_pendingTasks = 0;
m_graphStatus = InvalidMedia;
m_error = QMediaPlayer::ResourceError;
} else {
// {36b73882-c2c8-11cf-8b46-00805f6cef60}
static const GUID iid_IFilterGraph2 = {
0x36b73882, 0xc2c8, 0x11cf, {0x8b, 0x46, 0x00, 0x80, 0x5f, 0x6c, 0xef, 0x60} };
m_graphStatus = Loading;
DirectShowUtils::CoInitializeIfNeeded();
m_graph = com_new<IFilterGraph2>(CLSID_FilterGraph, iid_IFilterGraph2);
m_graphBuilder = com_new<ICaptureGraphBuilder2>(CLSID_CaptureGraphBuilder2, IID_ICaptureGraphBuilder2);
// Attach the filter graph to the capture graph.
HRESULT hr = m_graphBuilder->SetFiltergraph(m_graph);
if (FAILED(hr)) {
qCWarning(qtDirectShowPlugin, "[0x%x] Failed to attach filter to capture graph", hr);
m_graphBuilder->Release();
m_graphBuilder = nullptr;
}
if (stream)
m_pendingTasks = SetStreamSource;
else
m_pendingTasks = SetUrlSource;
::SetEvent(m_taskHandle);
}
m_playerControl->updateError(m_error, m_errorString);
m_playerControl->updateMediaInfo(m_duration, m_streamTypes, m_seekable);
m_playerControl->updateState(QMediaPlayer::StoppedState);
m_playerControl->updatePosition(m_position);
updateStatus();
}
void DirectShowPlayerService::doSetUrlSource(QMutexLocker *locker)
{
IBaseFilter *source = nullptr;
HRESULT hr = E_FAIL;
if (m_url.scheme() == QLatin1String("http") || m_url.scheme() == QLatin1String("https")) {
static const GUID clsid_WMAsfReader = {
0x187463a0, 0x5bb7, 0x11d3, {0xac, 0xbe, 0x00, 0x80, 0xc7, 0x5e, 0x24, 0x6e} };
// {56a868a6-0ad4-11ce-b03a-0020af0ba770}
static const GUID iid_IFileSourceFilter = {
0x56a868a6, 0x0ad4, 0x11ce, {0xb0, 0x3a, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} };
if (IFileSourceFilter *fileSource = com_new<IFileSourceFilter>(clsid_WMAsfReader, iid_IFileSourceFilter)) {
locker->unlock();
hr = fileSource->Load(reinterpret_cast<const OLECHAR *>(m_url.toString().utf16()), nullptr);
if (SUCCEEDED(hr)) {
source = com_cast<IBaseFilter>(fileSource, IID_IBaseFilter);
if (!SUCCEEDED(hr = m_graph->AddFilter(source, L"Source")) && source) {
source->Release();
source = nullptr;
}
}
fileSource->Release();
locker->relock();
}
}
if (!SUCCEEDED(hr)) {
locker->unlock();
const QString urlString = m_url.isLocalFile()
? QDir::toNativeSeparators(m_url.toLocalFile()) : m_url.toString();
hr = m_graph->AddSourceFilter(
reinterpret_cast<const OLECHAR *>(urlString.utf16()), L"Source", &source);
locker->relock();
}
if (SUCCEEDED(hr)) {
m_executedTasks = SetSource;
m_pendingTasks |= Render;
if (m_audioOutput)
m_pendingTasks |= SetAudioOutput;
if (m_videoOutput)
m_pendingTasks |= SetVideoOutput;
if (m_audioProbeControl)
m_pendingTasks |= SetAudioProbe;
if (m_videoProbeControl)
m_pendingTasks |= SetVideoProbe;
if (m_rate != 1.0)
m_pendingTasks |= SetRate;
m_source = source;
} else {
m_graphStatus = InvalidMedia;
switch (hr) {
case VFW_E_UNKNOWN_FILE_TYPE:
m_error = QMediaPlayer::FormatError;
m_errorString = QString();
break;
default:
m_error = QMediaPlayer::ResourceError;
m_errorString = QString();
qWarning("DirectShowPlayerService::doSetUrlSource: Unresolved error code 0x%x (%s)",
uint(hr), qPrintable(comError(hr)));
break;
}
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(Error)));
}
}
void DirectShowPlayerService::doSetStreamSource(QMutexLocker *locker)
{
Q_UNUSED(locker)
DirectShowIOSource *source = new DirectShowIOSource(m_loop);
source->setDevice(m_stream);
const HRESULT hr = m_graph->AddFilter(source, L"Source");
if (SUCCEEDED(hr)) {
m_executedTasks = SetSource;
m_pendingTasks |= Render;
if (m_audioOutput)
m_pendingTasks |= SetAudioOutput;
if (m_videoOutput)
m_pendingTasks |= SetVideoOutput;
if (m_rate != 1.0)
m_pendingTasks |= SetRate;
m_source = source;
} else {
source->Release();
m_pendingTasks = 0;
m_graphStatus = InvalidMedia;
m_error = QMediaPlayer::ResourceError;
m_errorString = QString();
qWarning("DirectShowPlayerService::doPlay: Unresolved error code 0x%x (%s)",
uint(hr), qPrintable(comError(hr)));
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(Error)));
}
}
void DirectShowPlayerService::doRender(QMutexLocker *locker)
{
m_pendingTasks |= m_executedTasks & (Play | Pause);
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
control->Stop();
control->Release();
}
if (m_pendingTasks & SetAudioOutput) {
m_graph->AddFilter(m_audioOutput, L"AudioOutput");
m_pendingTasks ^= SetAudioOutput;
m_executedTasks |= SetAudioOutput;
}
if (m_pendingTasks & SetVideoOutput) {
m_graph->AddFilter(m_videoOutput, L"VideoOutput");
m_pendingTasks ^= SetVideoOutput;
m_executedTasks |= SetVideoOutput;
}
if (m_pendingTasks & SetAudioProbe) {
doSetAudioProbe(locker);
m_pendingTasks ^= SetAudioProbe;
m_executedTasks |= SetAudioProbe;
}
if (m_pendingTasks & SetVideoProbe) {
doSetVideoProbe(locker);
m_pendingTasks ^= SetVideoProbe;
m_executedTasks |= SetVideoProbe;
}
IFilterGraph2 *graph = m_graph;
graph->AddRef();
QVarLengthArray<IBaseFilter *, 16> filters;
m_source->AddRef();
filters.append(m_source);
bool rendered = false;
HRESULT renderHr = S_OK;
while (!filters.isEmpty()) {
IEnumPins *pins = nullptr;
IBaseFilter *filter = filters[filters.size() - 1];
filters.removeLast();
if (!(m_pendingTasks & ReleaseFilters) && SUCCEEDED(filter->EnumPins(&pins))) {
int outputs = 0;
for (IPin *pin = nullptr; pins->Next(1, &pin, nullptr) == S_OK; pin->Release()) {
PIN_DIRECTION direction;
if (pin->QueryDirection(&direction) == S_OK && direction == PINDIR_OUTPUT) {
++outputs;
IPin *peer = nullptr;
if (pin->ConnectedTo(&peer) == S_OK) {
PIN_INFO peerInfo;
if (SUCCEEDED(peer->QueryPinInfo(&peerInfo)))
filters.append(peerInfo.pFilter);
peer->Release();
} else {
locker->unlock();
HRESULT hr = graph->RenderEx(pin, /*AM_RENDEREX_RENDERTOEXISTINGRENDERERS*/ 1, nullptr);
if (SUCCEEDED(hr)) {
rendered = true;
m_error = QMediaPlayer::NoError;
} else if (!(m_executedTasks & SetVideoOutput)) {
// Do not return an error if no video output is set yet.
rendered = true;
// Remember the error in this case.
// Handle it when playing is requested and no video output has been provided.
m_error = QMediaPlayer::ResourceError;
m_errorString = QString("%1: %2").arg(__FUNCTION__).arg(qt_error_string(hr));
} else if (renderHr == S_OK || renderHr == VFW_E_NO_DECOMPRESSOR){
renderHr = hr;
}
locker->relock();
}
}
}
pins->Release();
if (outputs == 0)
rendered = true;
}
filter->Release();
}
if (m_audioOutput && !isConnected(m_audioOutput, PINDIR_INPUT)) {
graph->RemoveFilter(m_audioOutput);
m_executedTasks &= ~SetAudioOutput;
}
if (m_videoOutput && !isConnected(m_videoOutput, PINDIR_INPUT)) {
graph->RemoveFilter(m_videoOutput);
m_executedTasks &= ~SetVideoOutput;
}
graph->Release();
if (!(m_pendingTasks & ReleaseFilters)) {
if (rendered) {
if (!(m_executedTasks & FinalizeLoad))
m_pendingTasks |= FinalizeLoad;
} else {
m_pendingTasks = 0;
m_graphStatus = InvalidMedia;
if (!m_audioOutput && !m_videoOutput) {
m_error = QMediaPlayer::ResourceError;
m_errorString = QString();
} else {
switch (renderHr) {
case VFW_E_UNSUPPORTED_AUDIO:
case VFW_E_UNSUPPORTED_VIDEO:
case VFW_E_UNSUPPORTED_STREAM:
m_error = QMediaPlayer::FormatError;
m_errorString = QString();
break;
default:
m_error = QMediaPlayer::ResourceError;
m_errorString = QString();
qWarning("DirectShowPlayerService::doRender: Unresolved error code 0x%x (%s)",
uint(renderHr), qPrintable(comError(renderHr)));
}
}
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(Error)));
}
m_executedTasks |= Render;
}
}
void DirectShowPlayerService::doFinalizeLoad(QMutexLocker *locker)
{
if (m_graphStatus != Loaded) {
if (IMediaEvent *event = com_cast<IMediaEvent>(m_graph, IID_IMediaEvent)) {
event->GetEventHandle(reinterpret_cast<OAEVENT *>(&m_eventHandle));
event->Release();
}
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
LONGLONG duration = 0;
seeking->GetDuration(&duration);
m_duration = duration / qt_directShowTimeScale;
DWORD capabilities = 0;
seeking->GetCapabilities(&capabilities);
m_seekable = capabilities & AM_SEEKING_CanSeekAbsolute;
seeking->Release();
}
}
if ((m_executedTasks & SetOutputs) == SetOutputs) {
m_streamTypes = AudioStream | VideoStream;
} else {
m_streamTypes = findStreamTypes(m_source);
}
m_executedTasks |= FinalizeLoad;
m_graphStatus = Loaded;
// Do not block gui thread while updating metadata from file.
locker->unlock();
DirectShowMetaDataControl::updateMetadata(m_url.toString(), m_metadata);
locker->relock();
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(FinalizedLoad)));
}
void DirectShowPlayerService::releaseGraph()
{
if (m_videoProbeControl)
m_videoProbeControl->flushVideoFrame();
if (m_graph) {
if (m_executingTask != 0) {
// {8E1C39A1-DE53-11cf-AA63-0080C744528D}
static const GUID iid_IAMOpenProgress = {
0x8E1C39A1, 0xDE53, 0x11cf, {0xAA, 0x63, 0x00, 0x80, 0xC7, 0x44, 0x52, 0x8D} };
if (IAMOpenProgress *progress = com_cast<IAMOpenProgress>(
m_graph, iid_IAMOpenProgress)) {
progress->AbortOperation();
progress->Release();
}
m_graph->Abort();
}
m_pendingTasks = ReleaseGraph;
::SetEvent(m_taskHandle);
m_loop->wait(&m_mutex);
DirectShowUtils::CoUninitializeIfNeeded();
}
}
void DirectShowPlayerService::doReleaseGraph(QMutexLocker *locker)
{
Q_UNUSED(locker);
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
control->Stop();
control->Release();
}
doReleaseAudioProbe(locker);
doReleaseVideoProbe(locker);
if (m_source) {
m_source->Release();
m_source = nullptr;
}
m_eventHandle = nullptr;
m_graph->Release();
m_graph = nullptr;
if (m_graphBuilder) {
m_graphBuilder->Release();
m_graphBuilder = nullptr;
}
m_loop->wake();
}
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers")
void DirectShowPlayerService::doSetVideoProbe(QMutexLocker *locker)
{
Q_UNUSED(locker);
if (!m_graph || !m_graphBuilder) {
qCWarning(qtDirectShowPlugin, "Attempting to set a video probe without a valid graph!");
return;
}
// Create the sample grabber, if necessary.
if (!m_videoSampleGrabber) {
m_videoSampleGrabber = new DirectShowSampleGrabber;
connect(m_videoSampleGrabber, &DirectShowSampleGrabber::bufferAvailable, this, &DirectShowPlayerService::onVideoBufferAvailable);
}
if (FAILED(m_graph->AddFilter(m_videoSampleGrabber->filter(), L"Video Sample Grabber"))) {
qCWarning(qtDirectShowPlugin, "Failed to add the video sample grabber into the graph!");
return;
}
DirectShowMediaType mediaType({ MEDIATYPE_Video, MEDIASUBTYPE_ARGB32 });
m_videoSampleGrabber->setMediaType(&mediaType);
// Connect source filter to sample grabber filter.
HRESULT hr = m_graphBuilder->RenderStream(nullptr, &MEDIATYPE_Video,
m_source, nullptr, m_videoSampleGrabber->filter());
if (FAILED(hr)) {
qCWarning(qtDirectShowPlugin, "[0x%x] Failed to connect the video sample grabber", hr);
return;
}
m_videoSampleGrabber->start(DirectShowSampleGrabber::CallbackMethod::BufferCB);
}
void DirectShowPlayerService::doSetAudioProbe(QMutexLocker *locker)
{
Q_UNUSED(locker);
if (!m_graph) {
qCWarning(qtDirectShowPlugin, "Attempting to set an audio probe without a valid graph!");
return;
}
// Create the sample grabber, if necessary.
if (!m_audioSampleGrabber) {
m_audioSampleGrabber = new DirectShowSampleGrabber;
connect(m_audioSampleGrabber, &DirectShowSampleGrabber::bufferAvailable, this, &DirectShowPlayerService::onAudioBufferAvailable);
}
static const AM_MEDIA_TYPE mediaType { MEDIATYPE_Audio, MEDIASUBTYPE_PCM };
m_audioSampleGrabber->setMediaType(&mediaType);
if (FAILED(m_graph->AddFilter(m_audioSampleGrabber->filter(), L"Audio Sample Grabber"))) {
qCWarning(qtDirectShowPlugin, "Failed to add the audio sample grabber into the graph!");
return;
}
if (!DirectShowUtils::connectFilters(m_graph, m_source, m_audioSampleGrabber->filter(), true)) {
// Connect source filter to sample grabber filter.
HRESULT hr = m_graphBuilder
? m_graphBuilder->RenderStream(nullptr, &MEDIATYPE_Audio,
m_source, nullptr, m_audioSampleGrabber->filter())
: E_FAIL;
if (FAILED(hr)) {
qCWarning(qtDirectShowPlugin, "[0x%x] Failed to connect the audio sample grabber", hr);
return;
}
}
m_audioSampleGrabber->start(DirectShowSampleGrabber::CallbackMethod::BufferCB);
}
QT_WARNING_POP
void DirectShowPlayerService::doReleaseVideoProbe(QMutexLocker *locker)
{
Q_UNUSED(locker);
if (!m_graph)
return;
if (!m_videoSampleGrabber)
return;
m_videoSampleGrabber->stop();
HRESULT hr = m_graph->RemoveFilter(m_videoSampleGrabber->filter());
if (FAILED(hr)) {
qCWarning(qtDirectShowPlugin, "Failed to remove the video sample grabber!");
return;
}
m_videoSampleGrabber->deleteLater();
m_videoSampleGrabber = nullptr;
}
void DirectShowPlayerService::doReleaseAudioProbe(QMutexLocker *locker)
{
Q_UNUSED(locker);
if (!m_graph)
return;
if (!m_audioSampleGrabber)
return;
m_audioSampleGrabber->stop();
HRESULT hr = m_graph->RemoveFilter(m_audioSampleGrabber->filter());
if (FAILED(hr)) {
qCWarning(qtDirectShowPlugin, "Failed to remove the audio sample grabber!");
return;
}
m_audioSampleGrabber->deleteLater();
m_audioSampleGrabber = nullptr;
}
int DirectShowPlayerService::findStreamTypes(IBaseFilter *source) const
{
QVarLengthArray<IBaseFilter *, 16> filters;
source->AddRef();
filters.append(source);
int streamTypes = 0;
while (!filters.isEmpty()) {
IEnumPins *pins = nullptr;
IBaseFilter *filter = filters[filters.size() - 1];
filters.removeLast();
if (SUCCEEDED(filter->EnumPins(&pins))) {
for (IPin *pin = nullptr; pins->Next(1, &pin, nullptr) == S_OK; pin->Release()) {
PIN_DIRECTION direction;
if (pin->QueryDirection(&direction) == S_OK && direction == PINDIR_OUTPUT) {
DirectShowMediaType connectionType;
if (SUCCEEDED(pin->ConnectionMediaType(&connectionType))) {
IPin *peer = nullptr;
if (connectionType->majortype == MEDIATYPE_Audio) {
streamTypes |= AudioStream;
} else if (connectionType->majortype == MEDIATYPE_Video) {
streamTypes |= VideoStream;
} else if (SUCCEEDED(pin->ConnectedTo(&peer))) {
PIN_INFO peerInfo;
if (SUCCEEDED(peer->QueryPinInfo(&peerInfo)))
filters.append(peerInfo.pFilter);
peer->Release();
}
} else {
streamTypes |= findStreamType(pin);
}
}
}
pins->Release();
}
filter->Release();
}
return streamTypes;
}
int DirectShowPlayerService::findStreamType(IPin *pin) const
{
IEnumMediaTypes *types;
if (SUCCEEDED(pin->EnumMediaTypes(&types))) {
bool video = false;
bool audio = false;
bool other = false;
for (AM_MEDIA_TYPE *type = nullptr;
types->Next(1, &type, nullptr) == S_OK;
DirectShowMediaType::deleteType(type)) {
if (type->majortype == MEDIATYPE_Audio)
audio = true;
else if (type->majortype == MEDIATYPE_Video)
video = true;
else
other = true;
}
types->Release();
if (other)
return 0;
else if (audio && !video)
return AudioStream;
else if (!audio && video)
return VideoStream;
else
return 0;
} else {
return 0;
}
}
void DirectShowPlayerService::play()
{
QMutexLocker locker(&m_mutex);
m_pendingTasks &= ~Pause;
m_pendingTasks |= Play;
if (m_executedTasks & Render) {
if (m_executedTasks & Stop) {
m_atEnd = false;
if (m_seekPosition == -1) {
m_dontCacheNextSeekResult = true;
m_seekPosition = 0;
m_position = 0;
m_pendingTasks |= Seek;
}
m_executedTasks ^= Stop;
}
::SetEvent(m_taskHandle);
}
updateStatus();
}
void DirectShowPlayerService::doPlay(QMutexLocker *locker)
{
// Invalidate if there is an error while loading.
if (m_error != QMediaPlayer::NoError) {
m_graphStatus = InvalidMedia;
if (!m_errorString.isEmpty())
qWarning("%s", qPrintable(m_errorString));
m_errorString = QString();
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(Error)));
return;
}
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
locker->unlock();
HRESULT hr = control->Run();
locker->relock();
control->Release();
if (SUCCEEDED(hr)) {
m_executedTasks |= Play;
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StatusChange)));
} else {
m_error = QMediaPlayer::ResourceError;
m_errorString = QString();
qWarning("DirectShowPlayerService::doPlay: Unresolved error code 0x%x (%s)",
uint(hr), qPrintable(comError(hr)));
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(Error)));
}
}
}
void DirectShowPlayerService::pause()
{
QMutexLocker locker(&m_mutex);
m_pendingTasks &= ~Play;
m_pendingTasks |= Pause;
if (m_executedTasks & Render) {
if (m_executedTasks & Stop) {
if (m_seekPosition == -1) {
m_dontCacheNextSeekResult = true;
m_seekPosition = 0;
m_position = 0;
m_pendingTasks |= Seek;
}
m_executedTasks ^= Stop;
}
::SetEvent(m_taskHandle);
}
updateStatus();
}
void DirectShowPlayerService::doPause(QMutexLocker *locker)
{
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
locker->unlock();
HRESULT hr = control->Pause();
locker->relock();
control->Release();
if (SUCCEEDED(hr)) {
IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking);
if (!m_atEnd && seeking) {
LONGLONG position = 0;
seeking->GetCurrentPosition(&position);
seeking->Release();
m_position = position / qt_directShowTimeScale;
} else {
m_position = 0;
m_atEnd = false;
}
m_executedTasks |= Pause;
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StatusChange)));
} else {
m_error = QMediaPlayer::ResourceError;
m_errorString = QString();
qWarning("DirectShowPlayerService::doPause: Unresolved error code 0x%x (%s)",
uint(hr), qPrintable(comError(hr)));
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(Error)));
}
}
}
void DirectShowPlayerService::stop()
{
QMutexLocker locker(&m_mutex);
m_pendingTasks &= ~(Play | Pause | Seek);
if ((m_executingTask | m_executedTasks) & (Play | Pause | Seek)) {
m_pendingTasks |= Stop;
if (m_videoProbeControl)
m_videoProbeControl->flushVideoFrame();
::SetEvent(m_taskHandle);
m_loop->wait(&m_mutex);
}
updateStatus();
}
void DirectShowPlayerService::doStop(QMutexLocker *locker)
{
Q_UNUSED(locker)
if (m_executedTasks & (Play | Pause)) {
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
control->Stop();
control->Release();
}
m_seekPosition = 0;
m_position = 0;
m_dontCacheNextSeekResult = true;
m_pendingTasks |= Seek;
m_executedTasks &= ~(Play | Pause);
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StatusChange)));
}
m_executedTasks |= Stop;
m_loop->wake();
}
void DirectShowPlayerService::setRate(qreal rate)
{
QMutexLocker locker(&m_mutex);
m_rate = rate;
m_pendingTasks |= SetRate;
if (m_executedTasks & FinalizeLoad)
::SetEvent(m_taskHandle);
}
void DirectShowPlayerService::doSetRate(QMutexLocker *locker)
{
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
// Cache current values as we can't query IMediaSeeking during a seek due to the
// possibility of a deadlock when flushing the VideoSurfaceFilter.
LONGLONG currentPosition = 0;
seeking->GetCurrentPosition(&currentPosition);
m_position = currentPosition / qt_directShowTimeScale;
LONGLONG minimum = 0;
LONGLONG maximum = 0;
m_playbackRange = SUCCEEDED(seeking->GetAvailable(&minimum, &maximum))
? QMediaTimeRange(minimum / qt_directShowTimeScale, maximum / qt_directShowTimeScale)
: QMediaTimeRange();
locker->unlock();
HRESULT hr = seeking->SetRate(m_rate);
locker->relock();
if (!SUCCEEDED(hr)) {
qWarning("%s: Audio device or filter does not support rate: %.2f. " \
"Falling back to previous value.", __FUNCTION__, m_rate);
double rate = 0.0;
m_rate = SUCCEEDED(seeking->GetRate(&rate))
? rate
: 1.0;
}
seeking->Release();
} else if (m_rate != 1.0) {
m_rate = 1.0;
}
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(RateChange)));
}
qint64 DirectShowPlayerService::position() const
{
QMutexLocker locker(const_cast<QMutex *>(&m_mutex));
if (m_graphStatus == Loaded) {
if (m_executingTask == Seek || m_executingTask == SetRate || (m_pendingTasks & Seek))
return m_position;
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
LONGLONG position = 0;
seeking->GetCurrentPosition(&position);
seeking->Release();
const_cast<qint64 &>(m_position) = position / qt_directShowTimeScale;
return m_position;
}
}
return 0;
}
QMediaTimeRange DirectShowPlayerService::availablePlaybackRanges() const
{
QMutexLocker locker(const_cast<QMutex *>(&m_mutex));
if (m_graphStatus == Loaded) {
if (m_executingTask == Seek || m_executingTask == SetRate || (m_pendingTasks & Seek))
return m_playbackRange;
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
LONGLONG minimum = 0;
LONGLONG maximum = 0;
HRESULT hr = seeking->GetAvailable(&minimum, &maximum);
seeking->Release();
if (SUCCEEDED(hr))
return QMediaTimeRange(minimum, maximum);
}
}
return QMediaTimeRange();
}
void DirectShowPlayerService::seek(qint64 position)
{
QMutexLocker locker(&m_mutex);
m_seekPosition = position;
m_pendingTasks |= Seek;
if (m_executedTasks & FinalizeLoad)
::SetEvent(m_taskHandle);
}
void DirectShowPlayerService::doSeek(QMutexLocker *locker)
{
if (m_seekPosition == -1)
return;
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
LONGLONG seekPosition = LONGLONG(m_seekPosition) * qt_directShowTimeScale;
// Cache current values as we can't query IMediaSeeking during a seek due to the
// possibility of a deadlock when flushing the VideoSurfaceFilter.
LONGLONG currentPosition = 0;
if (!m_dontCacheNextSeekResult) {
seeking->GetCurrentPosition(&currentPosition);
m_position = currentPosition / qt_directShowTimeScale;
}
LONGLONG minimum = 0;
LONGLONG maximum = 0;
m_playbackRange = SUCCEEDED(seeking->GetAvailable(&minimum, &maximum))
? QMediaTimeRange(
minimum / qt_directShowTimeScale, maximum / qt_directShowTimeScale)
: QMediaTimeRange();
locker->unlock();
seeking->SetPositions(
&seekPosition, AM_SEEKING_AbsolutePositioning, nullptr, AM_SEEKING_NoPositioning);
locker->relock();
if (!m_dontCacheNextSeekResult) {
seeking->GetCurrentPosition(&currentPosition);
m_position = currentPosition / qt_directShowTimeScale;
}
seeking->Release();
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(PositionChange)));
}
m_seekPosition = -1;
m_dontCacheNextSeekResult = false;
}
int DirectShowPlayerService::bufferStatus() const
{
#if QT_CONFIG(wmsdk)
QMutexLocker locker(const_cast<QMutex *>(&m_mutex));
if (IWMReaderAdvanced2 *reader = com_cast<IWMReaderAdvanced2>(
m_source, IID_IWMReaderAdvanced2)) {
DWORD percentage = 0;
reader->GetBufferProgress(&percentage, nullptr);
reader->Release();
return percentage;
}
return 0;
#else
return 0;
#endif
}
void DirectShowPlayerService::setAudioOutput(IBaseFilter *filter)
{
QMutexLocker locker(&m_mutex);
if (m_graph) {
if (m_audioOutput) {
if (m_executedTasks & SetAudioOutput) {
m_pendingTasks |= ReleaseAudioOutput;
::SetEvent(m_taskHandle);
m_loop->wait(&m_mutex);
}
m_audioOutput->Release();
}
m_audioOutput = filter;
if (m_audioOutput) {
m_audioOutput->AddRef();
m_pendingTasks |= SetAudioOutput;
if (m_executedTasks & SetSource) {
m_pendingTasks |= Render;
::SetEvent(m_taskHandle);
}
} else {
m_pendingTasks &= ~ SetAudioOutput;
}
} else {
if (m_audioOutput)
m_audioOutput->Release();
m_audioOutput = filter;
if (m_audioOutput)
m_audioOutput->AddRef();
}
m_playerControl->updateAudioOutput(m_audioOutput);
}
void DirectShowPlayerService::doReleaseAudioOutput(QMutexLocker *locker)
{
Q_UNUSED(locker)
m_pendingTasks |= m_executedTasks & (Play | Pause);
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
control->Stop();
control->Release();
}
IBaseFilter *decoder = getConnected(m_audioOutput, PINDIR_INPUT);
if (!decoder) {
decoder = m_audioOutput;
decoder->AddRef();
}
// {DCFBDCF6-0DC2-45f5-9AB2-7C330EA09C29}
static const GUID iid_IFilterChain = {
0xDCFBDCF6, 0x0DC2, 0x45f5, {0x9A, 0xB2, 0x7C, 0x33, 0x0E, 0xA0, 0x9C, 0x29} };
if (IFilterChain *chain = com_cast<IFilterChain>(m_graph, iid_IFilterChain)) {
chain->RemoveChain(decoder, m_audioOutput);
chain->Release();
} else {
m_graph->RemoveFilter(m_audioOutput);
}
decoder->Release();
m_executedTasks &= ~SetAudioOutput;
m_loop->wake();
}
void DirectShowPlayerService::setVideoOutput(IBaseFilter *filter)
{
QMutexLocker locker(&m_mutex);
if (m_graph) {
if (m_videoOutput) {
if (m_executedTasks & SetVideoOutput) {
m_pendingTasks |= ReleaseVideoOutput;
::SetEvent(m_taskHandle);
m_loop->wait(&m_mutex);
}
m_videoOutput->Release();
}
m_videoOutput = filter;
if (m_videoOutput) {
m_videoOutput->AddRef();
m_pendingTasks |= SetVideoOutput;
if (m_executedTasks & SetSource) {
m_pendingTasks |= Render;
::SetEvent(m_taskHandle);
}
}
} else {
if (m_videoOutput)
m_videoOutput->Release();
m_videoOutput = filter;
if (m_videoOutput)
m_videoOutput->AddRef();
}
}
void DirectShowPlayerService::updateAudioProbe()
{
QMutexLocker locker(&m_mutex);
// Set/Activate the audio probe.
if (m_graph) {
// If we don't have a audio probe, then stop and release the audio sample grabber
if (!m_audioProbeControl && (m_executedTasks & SetAudioProbe)) {
m_pendingTasks |= ReleaseAudioProbe;
::SetEvent(m_taskHandle);
m_loop->wait(&m_mutex);
} else if (m_audioProbeControl) {
m_pendingTasks |= SetAudioProbe;
}
}
}
void DirectShowPlayerService::updateVideoProbe()
{
QMutexLocker locker(&m_mutex);
// Set/Activate the video probe.
if (m_graph) {
// If we don't have a video probe, then stop and release the video sample grabber
if (!m_videoProbeControl && (m_executedTasks & SetVideoProbe)) {
m_pendingTasks |= ReleaseVideoProbe;
::SetEvent(m_taskHandle);
m_loop->wait(&m_mutex);
} else if (m_videoProbeControl){
m_pendingTasks |= SetVideoProbe;
}
}
}
void DirectShowPlayerService::doReleaseVideoOutput(QMutexLocker *locker)
{
Q_UNUSED(locker)
m_pendingTasks |= m_executedTasks & (Play | Pause);
if (IMediaControl *control = com_cast<IMediaControl>(m_graph, IID_IMediaControl)) {
control->Stop();
control->Release();
}
IBaseFilter *intermediate = nullptr;
if (!SUCCEEDED(m_graph->FindFilterByName(L"Color Space Converter", &intermediate))) {
intermediate = m_videoOutput;
intermediate->AddRef();
}
IBaseFilter *decoder = getConnected(intermediate, PINDIR_INPUT);
if (!decoder) {
decoder = intermediate;
decoder->AddRef();
}
// {DCFBDCF6-0DC2-45f5-9AB2-7C330EA09C29}
static const GUID iid_IFilterChain = {
0xDCFBDCF6, 0x0DC2, 0x45f5, {0x9A, 0xB2, 0x7C, 0x33, 0x0E, 0xA0, 0x9C, 0x29} };
if (IFilterChain *chain = com_cast<IFilterChain>(m_graph, iid_IFilterChain)) {
chain->RemoveChain(decoder, m_videoOutput);
chain->Release();
} else {
m_graph->RemoveFilter(m_videoOutput);
}
intermediate->Release();
decoder->Release();
m_executedTasks &= ~SetVideoOutput;
m_loop->wake();
}
void DirectShowPlayerService::customEvent(QEvent *event)
{
if (event->type() == QEvent::Type(FinalizedLoad)) {
QMutexLocker locker(&m_mutex);
m_playerControl->updateMediaInfo(m_duration, m_streamTypes, m_seekable);
if (m_metadata.isEmpty())
DirectShowMetaDataControl::updateMetadata(m_graph, m_source, m_metadata);
m_metaDataControl->setMetadata(m_metadata);
m_metadata.clear();
updateStatus();
} else if (event->type() == QEvent::Type(Error)) {
QMutexLocker locker(&m_mutex);
if (m_error != QMediaPlayer::NoError) {
m_playerControl->updateError(m_error, m_errorString);
m_playerControl->updateMediaInfo(m_duration, m_streamTypes, m_seekable);
m_playerControl->updateState(QMediaPlayer::StoppedState);
updateStatus();
}
} else if (event->type() == QEvent::Type(RateChange)) {
QMutexLocker locker(&m_mutex);
m_playerControl->updatePlaybackRate(m_rate);
} else if (event->type() == QEvent::Type(StatusChange)) {
QMutexLocker locker(&m_mutex);
updateStatus();
m_playerControl->updatePosition(m_position);
} else if (event->type() == QEvent::Type(DurationChange)) {
QMutexLocker locker(&m_mutex);
m_playerControl->updateMediaInfo(m_duration, m_streamTypes, m_seekable);
} else if (event->type() == QEvent::Type(EndOfMedia)) {
QMutexLocker locker(&m_mutex);
if (m_atEnd) {
m_playerControl->updateState(QMediaPlayer::StoppedState);
m_playerControl->updateStatus(QMediaPlayer::EndOfMedia);
m_playerControl->updatePosition(m_position);
if (m_videoProbeControl)
m_videoProbeControl->flushVideoFrame();
}
} else if (event->type() == QEvent::Type(PositionChange)) {
QMutexLocker locker(&m_mutex);
if (m_playerControl->mediaStatus() == QMediaPlayer::EndOfMedia)
m_playerControl->updateStatus(QMediaPlayer::LoadedMedia);
m_playerControl->updatePosition(m_position);
// Emits only when seek has been performed.
if (m_videoRendererControl)
emit m_videoRendererControl->positionChanged(m_position);
} else {
QMediaService::customEvent(event);
}
}
void DirectShowPlayerService::videoOutputChanged()
{
setVideoOutput(m_videoRendererControl->filter());
}
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers")
void DirectShowPlayerService::onAudioBufferAvailable(double time, const QByteArray &data)
{
QMutexLocker locker(&m_mutex);
if (!m_audioProbeControl || !m_audioSampleGrabber)
return;
DirectShowMediaType mt(AM_MEDIA_TYPE { GUID_NULL });
const bool ok = m_audioSampleGrabber->getConnectedMediaType(&mt);
if (!ok)
return;
if (mt->majortype != MEDIATYPE_Audio)
return;
if (mt->subtype != MEDIASUBTYPE_PCM)
return;
const bool isWfx = ((mt->formattype == FORMAT_WaveFormatEx) && (mt->cbFormat >= sizeof(WAVEFORMATEX)));
WAVEFORMATEX *wfx = isWfx ? reinterpret_cast<WAVEFORMATEX *>(mt->pbFormat) : nullptr;
if (!wfx)
return;
if (wfx->wFormatTag != WAVE_FORMAT_PCM && wfx->wFormatTag != WAVE_FORMAT_EXTENSIBLE)
return;
if ((wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) && (wfx->cbSize >= sizeof(WAVEFORMATEXTENSIBLE))) {
WAVEFORMATEXTENSIBLE *wfxe = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(wfx);
if (wfxe->SubFormat != KSDATAFORMAT_SUBTYPE_PCM)
return;
}
QAudioFormat format;
format.setSampleRate(wfx->nSamplesPerSec);
format.setChannelCount(wfx->nChannels);
format.setSampleSize(wfx->wBitsPerSample);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
if (format.sampleSize() == 8)
format.setSampleType(QAudioFormat::UnSignedInt);
else
format.setSampleType(QAudioFormat::SignedInt);
const quint64 startTime = quint64(time * 1000.);
QAudioBuffer audioBuffer(data,
format,
startTime);
Q_EMIT m_audioProbeControl->audioBufferProbed(audioBuffer);
}
void DirectShowPlayerService::onVideoBufferAvailable(double time, const QByteArray &data)
{
Q_UNUSED(time);
QMutexLocker locker(&m_mutex);
if (!m_videoProbeControl || !m_videoSampleGrabber)
return;
DirectShowMediaType mt(AM_MEDIA_TYPE { GUID_NULL });
const bool ok = m_videoSampleGrabber->getConnectedMediaType(&mt);
if (!ok)
return;
if (mt->majortype != MEDIATYPE_Video)
return;
QVideoFrame::PixelFormat format = DirectShowMediaType::pixelFormatFromType(&mt);
if (format == QVideoFrame::Format_Invalid) {
qCWarning(qtDirectShowPlugin, "Invalid format, stopping video probes!");
m_videoSampleGrabber->stop();
return;
}
const QVideoSurfaceFormat &videoFormat = DirectShowMediaType::videoFormatFromType(&mt);
if (!videoFormat.isValid())
return;
const QSize &size = videoFormat.frameSize();
const int bytesPerLine = DirectShowMediaType::bytesPerLine(videoFormat);
QVideoFrame frame(new QMemoryVideoBuffer(data, bytesPerLine),
size,
format);
m_videoProbeControl->probeVideoFrame(frame);
}
QT_WARNING_POP
void DirectShowPlayerService::graphEvent(QMutexLocker *locker)
{
Q_UNUSED(locker)
if (IMediaEvent *event = com_cast<IMediaEvent>(m_graph, IID_IMediaEvent)) {
long eventCode;
LONG_PTR param1;
LONG_PTR param2;
while (event->GetEvent(&eventCode, &param1, &param2, 0) == S_OK) {
switch (eventCode) {
case EC_BUFFERING_DATA:
m_buffering = param1;
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StatusChange)));
break;
case EC_COMPLETE:
m_executedTasks &= ~(Play | Pause);
m_executedTasks |= Stop;
m_buffering = false;
m_atEnd = true;
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
LONGLONG position = 0;
seeking->GetCurrentPosition(&position);
seeking->Release();
m_position = position / qt_directShowTimeScale;
}
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(EndOfMedia)));
break;
case EC_LENGTH_CHANGED:
if (IMediaSeeking *seeking = com_cast<IMediaSeeking>(m_graph, IID_IMediaSeeking)) {
LONGLONG duration = 0;
seeking->GetDuration(&duration);
m_duration = duration / qt_directShowTimeScale;
DWORD capabilities = 0;
seeking->GetCapabilities(&capabilities);
m_seekable = capabilities & AM_SEEKING_CanSeekAbsolute;
seeking->Release();
QCoreApplication::postEvent(this, new QEvent(QEvent::Type(DurationChange)));
}
break;
default:
break;
}
event->FreeEventParams(eventCode, param1, param2);
}
event->Release();
}
}
void DirectShowPlayerService::updateStatus()
{
switch (m_graphStatus) {
case NoMedia:
m_playerControl->updateStatus(QMediaPlayer::NoMedia);
break;
case Loading:
m_playerControl->updateStatus(QMediaPlayer::LoadingMedia);
break;
case Loaded:
if ((m_executingTask | m_executedTasks) & (Play | Pause)) {
if (m_buffering)
m_playerControl->updateStatus(QMediaPlayer::BufferingMedia);
else
m_playerControl->updateStatus(QMediaPlayer::BufferedMedia);
} else {
m_playerControl->updateStatus(QMediaPlayer::LoadedMedia);
}
break;
case InvalidMedia:
m_playerControl->updateStatus(QMediaPlayer::InvalidMedia);
break;
default:
m_playerControl->updateStatus(QMediaPlayer::UnknownMediaStatus);
}
}
bool DirectShowPlayerService::isConnected(IBaseFilter *filter, PIN_DIRECTION direction) const
{
bool connected = false;
IEnumPins *pins = nullptr;
if (SUCCEEDED(filter->EnumPins(&pins))) {
for (IPin *pin = nullptr; pins->Next(1, &pin, nullptr) == S_OK; pin->Release()) {
PIN_DIRECTION dir;
if (SUCCEEDED(pin->QueryDirection(&dir)) && dir == direction) {
IPin *peer = nullptr;
if (SUCCEEDED(pin->ConnectedTo(&peer))) {
connected = true;
peer->Release();
}
}
}
pins->Release();
}
return connected;
}
IBaseFilter *DirectShowPlayerService::getConnected(
IBaseFilter *filter, PIN_DIRECTION direction) const
{
IBaseFilter *connected = nullptr;
IEnumPins *pins = nullptr;
if (SUCCEEDED(filter->EnumPins(&pins))) {
for (IPin *pin = nullptr; pins->Next(1, &pin, nullptr) == S_OK; pin->Release()) {
PIN_DIRECTION dir;
if (SUCCEEDED(pin->QueryDirection(&dir)) && dir == direction) {
IPin *peer = nullptr;
if (SUCCEEDED(pin->ConnectedTo(&peer))) {
PIN_INFO info;
if (SUCCEEDED(peer->QueryPinInfo(&info))) {
if (connected) {
qWarning("DirectShowPlayerService::getConnected: "
"Multiple connected filters");
connected->Release();
}
connected = info.pFilter;
}
peer->Release();
}
}
}
pins->Release();
}
return connected;
}
void DirectShowPlayerService::run()
{
QMutexLocker locker(&m_mutex);
for (;;) {
while (m_pendingTasks == 0) {
DWORD result = 0;
locker.unlock();
if (m_eventHandle) {
HANDLE handles[] = { m_taskHandle, m_eventHandle };
result = ::WaitForMultipleObjects(2, handles, false, INFINITE);
} else {
result = ::WaitForSingleObject(m_taskHandle, INFINITE);
}
locker.relock();
if (result == WAIT_OBJECT_0 + 1) {
graphEvent(&locker);
}
}
if (m_pendingTasks & ReleaseGraph) {
m_pendingTasks ^= ReleaseGraph;
m_executingTask = ReleaseGraph;
doReleaseGraph(&locker);
//if the graph is released, we should not process other operations later
if (m_pendingTasks & Shutdown) {
m_pendingTasks = 0;
return;
}
m_pendingTasks = 0;
} else if (m_pendingTasks & Shutdown) {
return;
} else if (m_pendingTasks & ReleaseAudioOutput) {
m_pendingTasks ^= ReleaseAudioOutput;
m_executingTask = ReleaseAudioOutput;
doReleaseAudioOutput(&locker);
} else if (m_pendingTasks & ReleaseVideoOutput) {
m_pendingTasks ^= ReleaseVideoOutput;
m_executingTask = ReleaseVideoOutput;
doReleaseVideoOutput(&locker);
} else if (m_pendingTasks & ReleaseAudioProbe) {
m_pendingTasks ^= ReleaseAudioProbe;
m_executingTask = ReleaseAudioProbe;
doReleaseAudioProbe(&locker);
} else if (m_pendingTasks & ReleaseVideoProbe) {
m_pendingTasks ^= ReleaseVideoProbe;
m_executingTask = ReleaseVideoProbe;
doReleaseVideoProbe(&locker);
} else if (m_pendingTasks & SetUrlSource) {
m_pendingTasks ^= SetUrlSource;
m_executingTask = SetUrlSource;
doSetUrlSource(&locker);
} else if (m_pendingTasks & SetStreamSource) {
m_pendingTasks ^= SetStreamSource;
m_executingTask = SetStreamSource;
doSetStreamSource(&locker);
} else if (m_pendingTasks & SetAudioProbe) {
m_pendingTasks ^= SetAudioProbe;
m_executingTask = SetAudioProbe;
doSetAudioProbe(&locker);
} else if (m_pendingTasks & SetVideoProbe) {
m_pendingTasks ^= SetVideoProbe;
m_executingTask = SetVideoProbe;
doSetVideoProbe(&locker);
} else if (m_pendingTasks & Render) {
m_pendingTasks ^= Render;
m_executingTask = Render;
doRender(&locker);
} else if (!(m_executedTasks & Render)) {
m_pendingTasks &= ~(FinalizeLoad | SetRate | Stop | Pause | Seek | Play);
} else if (m_pendingTasks & FinalizeLoad) {
m_pendingTasks ^= FinalizeLoad;
m_executingTask = FinalizeLoad;
doFinalizeLoad(&locker);
} else if (m_pendingTasks & Stop) {
m_pendingTasks ^= Stop;
m_executingTask = Stop;
doStop(&locker);
} else if (m_pendingTasks & SetRate) {
m_pendingTasks ^= SetRate;
m_executingTask = SetRate;
doSetRate(&locker);
} else if (m_pendingTasks & Pause) {
m_pendingTasks ^= Pause;
m_executingTask = Pause;
doPause(&locker);
} else if (m_pendingTasks & Seek) {
m_pendingTasks ^= Seek;
m_executingTask = Seek;
doSeek(&locker);
} else if (m_pendingTasks & Play) {
m_pendingTasks ^= Play;
m_executingTask = Play;
doPlay(&locker);
}
m_executingTask = 0;
}
}
QT_END_NAMESPACE