/* This file is part of the KDE project. | |
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). | |
This library is free software: you can redistribute it and/or modify | |
it under the terms of the GNU Lesser General Public License as published by | |
the Free Software Foundation, either version 2.1 or 3 of the License. | |
This library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General Public License | |
along with this library. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
#include "fakesource.h" | |
#include "iodevicereader.h" | |
#include "qaudiocdreader.h" | |
#include "mediagraph.h" | |
#include "mediaobject.h" | |
#include <QtCore/QUrl> | |
#include <QtCore/QDebug> | |
#include <qnetwork.h> | |
QT_BEGIN_NAMESPACE | |
namespace Phonon | |
{ | |
namespace DS9 | |
{ | |
//description of a connection | |
struct GraphConnection | |
{ | |
Filter output; | |
int outputOffset; | |
Filter input; | |
int inputOffset; | |
}; | |
static QList<GraphConnection> getConnections(Filter source) | |
{ | |
QList<GraphConnection> ret; | |
int outOffset = 0; | |
const QList<OutputPin> outputs = BackendNode::pins(source, PINDIR_OUTPUT); | |
for (int i = 0; i < outputs.count(); ++i) { | |
InputPin input; | |
if (outputs.at(i)->ConnectedTo(input.pparam()) == S_OK) { | |
PIN_INFO info; | |
input->QueryPinInfo(&info); | |
Filter current(info.pFilter); | |
if (current) { | |
//this is a valid connection | |
const int inOffset = BackendNode::pins(current, PINDIR_INPUT).indexOf(input); | |
const GraphConnection connection = {source, outOffset, current, inOffset}; | |
ret += connection; | |
ret += getConnections(current); //get subsequent connections | |
} | |
} | |
outOffset++; | |
} | |
return ret; | |
} | |
/* | |
static HRESULT saveToFile(Graph graph, const QString &filepath) | |
{ | |
const WCHAR wszStreamName[] = L"ActiveMovieGraph"; | |
HRESULT hr; | |
ComPointer<IStorage> storage; | |
// First, create a document file that will hold the GRF file | |
hr = StgCreateDocfile((OLECHAR*)filepath.utf16(), | |
STGM_CREATE | STGM_TRANSACTED | STGM_READWRITE | | |
STGM_SHARE_EXCLUSIVE, | |
0, storage.pparam()); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
// Next, create a stream to store. | |
ComPointer<IStream> stream; | |
hr = storage->CreateStream(wszStreamName, | |
STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, | |
0, 0, stream.pparam()); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
// The IpersistStream::Save method converts a stream into a persistent object. | |
ComPointer<IPersistStream> persist(graph, IID_IPersistStream); | |
hr = persist->Save(stream, TRUE); | |
if (SUCCEEDED(hr)) { | |
hr = storage->Commit(STGC_DEFAULT); | |
} | |
return hr; | |
} | |
*/ | |
MediaGraph::MediaGraph(MediaObject *mo, short index) : | |
m_graph(CLSID_FilterGraph, IID_IGraphBuilder), | |
m_fakeSource(new FakeSource()), | |
m_hasVideo(false), m_hasAudio(false), m_connectionsDirty(false), | |
m_isStopping(false), m_isSeekable(false), m_result(S_OK), | |
m_index(index), m_renderId(0), m_seekId(0), | |
m_currentTime(0), m_totalTime(0), m_mediaObject(mo) | |
{ | |
m_mediaControl = ComPointer<IMediaControl>(m_graph, IID_IMediaControl); | |
Q_ASSERT(m_mediaControl); | |
m_mediaSeeking = ComPointer<IMediaSeeking>(m_graph, IID_IMediaSeeking); | |
Q_ASSERT(m_mediaSeeking); | |
HRESULT hr = m_graph->AddFilter(m_fakeSource, 0); | |
if (m_mediaObject->catchComError(hr)) { | |
return; | |
} | |
} | |
MediaGraph::~MediaGraph() | |
{ | |
} | |
short MediaGraph::index() const | |
{ | |
return m_index; | |
} | |
void MediaGraph::grabNode(BackendNode *node) | |
{ | |
grabFilter(node->filter(m_index)); | |
} | |
void MediaGraph::grabFilter(Filter filter) | |
{ | |
if (filter) { | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
if (info.pGraph != m_graph) { | |
if (info.pGraph) { | |
m_mediaObject->catchComError(info.pGraph->RemoveFilter(filter)); | |
} | |
m_mediaObject->catchComError(m_graph->AddFilter(filter, 0)); | |
} | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
} | |
} | |
} | |
void MediaGraph::switchFilters(Filter oldFilter, Filter newFilter) | |
{ | |
OAFilterState state = syncGetRealState(); | |
if (state != State_Stopped) { | |
ensureStopped(); //to do the transaction | |
} | |
OutputPin connected; | |
{ | |
InputPin pin = BackendNode::pins(oldFilter, PINDIR_INPUT).first(); | |
pin->ConnectedTo(connected.pparam()); | |
} | |
m_graph->RemoveFilter(oldFilter); | |
m_graph->AddFilter(newFilter, 0); | |
if (connected) { | |
InputPin pin = BackendNode::pins(newFilter, PINDIR_INPUT).first(); | |
//let's reestablish the connections | |
m_graph->Connect(connected, pin); | |
} | |
switch(state) | |
{ | |
case State_Running: | |
play(); | |
break; | |
case State_Paused: | |
pause(); | |
break; | |
default: | |
break; | |
} | |
} | |
OAFilterState MediaGraph::syncGetRealState() const | |
{ | |
OAFilterState state; | |
m_mediaControl->GetState(INFINITE, &state); | |
return state; | |
} | |
void MediaGraph::ensureSourceDisconnected() | |
{ | |
for (int i = 0; i < m_sinkConnections.count(); ++i) { | |
const Filter currentFilter = m_sinkConnections.at(i)->filter(m_index); | |
const QList<InputPin> inputs = BackendNode::pins(currentFilter, PINDIR_INPUT); | |
const QList<InputPin> outputs = BackendNode::pins(m_fakeSource, PINDIR_OUTPUT); | |
for (int i = 0; i < inputs.count(); ++i) { | |
for (int o = 0; o < outputs.count(); o++) { | |
tryDisconnect(outputs.at(o), inputs.at(i)); | |
} | |
for (int d = 0; d < m_decoderPins.count(); ++d) { | |
tryDisconnect(m_decoderPins.at(d), inputs.at(i)); | |
} | |
} | |
} | |
} | |
void MediaGraph::ensureSourceConnectedTo(bool force) | |
{ | |
if (m_connectionsDirty == false && force == false) { | |
return; | |
} | |
m_connectionsDirty = false; | |
ensureSourceDisconnected(); | |
//reconnect the pins | |
for (int i = 0; i < m_sinkConnections.count(); ++i) { | |
const Filter currentFilter = m_sinkConnections.at(i)->filter(m_index); | |
const QList<InputPin> inputs = BackendNode::pins(currentFilter, PINDIR_INPUT); | |
for(int i = 0; i < inputs.count(); ++i) { | |
//we ensure the filter belongs to the graph | |
grabFilter(currentFilter); | |
for (int d = 0; d < m_decoderPins.count(); ++d) { | |
//a decoder has only one output | |
if (tryConnect(m_decoderPins.at(d), inputs.at(i))) { | |
break; | |
} | |
} | |
} | |
} | |
} | |
QList<Filter> MediaGraph::getAllFilters(Graph graph) | |
{ | |
QList<Filter> ret; | |
ComPointer<IEnumFilters> enumFilters; | |
graph->EnumFilters(enumFilters.pparam()); | |
Filter current; | |
while( enumFilters && enumFilters->Next(1, current.pparam(), 0) == S_OK) { | |
ret += current; | |
} | |
return ret; | |
} | |
QList<Filter> MediaGraph::getAllFilters() const | |
{ | |
return getAllFilters(m_graph); | |
} | |
bool MediaGraph::isSeekable() const | |
{ | |
return m_isSeekable; | |
} | |
qint64 MediaGraph::absoluteTotalTime() const | |
{ | |
if (m_seekId) { | |
return m_totalTime; | |
} else { | |
qint64 ret = 0; | |
if (m_mediaSeeking) { | |
m_mediaSeeking->GetDuration(&ret); | |
ret /= 10000; //convert to milliseconds | |
} | |
return ret; | |
} | |
} | |
qint64 MediaGraph::absoluteCurrentTime() const | |
{ | |
if (m_seekId) { | |
return m_currentTime; | |
} else { | |
qint64 ret = -1; | |
if (m_mediaSeeking) { | |
HRESULT hr = m_mediaSeeking->GetCurrentPosition(&ret); | |
if (FAILED(hr)) { | |
return ret; | |
} | |
ret /= 10000; //convert to milliseconds | |
} | |
return ret; | |
} | |
} | |
Phonon::MediaSource MediaGraph::mediaSource() const | |
{ | |
return m_mediaSource; | |
} | |
void MediaGraph::play() | |
{ | |
ensureSourceConnectedTo(); | |
m_mediaObject->workerThread()->addStateChangeRequest(m_graph, State_Running, m_decoders); | |
} | |
void MediaGraph::pause() | |
{ | |
ensureSourceConnectedTo(); | |
m_mediaObject->workerThread()->addStateChangeRequest(m_graph, State_Paused, m_decoders); | |
} | |
HRESULT MediaGraph::renderResult() const | |
{ | |
return m_result; | |
} | |
bool MediaGraph::isStopping() const | |
{ | |
return m_isStopping; | |
} | |
Graph MediaGraph::graph() const | |
{ | |
return m_graph; | |
} | |
void MediaGraph::stop() | |
{ | |
if (!isLoading()) { | |
ensureStopped(); | |
absoluteSeek(0); //resets the clock | |
} else { | |
m_mediaObject->workerThread()->abortCurrentRender(m_renderId); | |
m_renderId = 0; //cancels current loading | |
} | |
m_mediaObject->workerThread()->addStateChangeRequest(m_graph, State_Stopped); | |
} | |
void MediaGraph::ensureStopped() | |
{ | |
m_isStopping = true; | |
//special case here because we want stopped to be synchronous | |
m_graph->Abort(); | |
m_mediaControl->Stop(); | |
OAFilterState dummy; | |
//this will wait until the change is effective | |
m_mediaControl->GetState(INFINITE, &dummy); | |
m_isStopping = false; | |
} | |
bool MediaGraph::isLoading() const | |
{ | |
return m_renderId != 0; | |
} | |
void MediaGraph::absoluteSeek(qint64 time) | |
{ | |
//this just sends a request | |
if (m_seekId == 0) { | |
m_currentTime = absoluteCurrentTime(); | |
m_totalTime = absoluteTotalTime(); | |
} | |
m_seekId = m_mediaObject->workerThread()->addSeekRequest(m_graph, time); | |
} | |
HRESULT MediaGraph::removeFilter(const Filter& filter) | |
{ | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
#ifdef GRAPH_DEBUG | |
qDebug() << "removeFilter" << QString((const QChar *)info.achName); | |
#endif | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
if (info.pGraph == m_graph) | |
return m_graph->RemoveFilter(filter); | |
} | |
//already removed | |
return S_OK; | |
} | |
HRESULT MediaGraph::cleanup() | |
{ | |
stop(); | |
ensureSourceDisconnected(); | |
QList<Filter> list = m_decoders; | |
if (m_demux) { | |
list << m_demux; | |
} | |
if (m_realSource) { | |
list << m_realSource; | |
} | |
list << m_decoders; | |
for (int i = 0; i < m_decoders.count(); ++i) { | |
list += getFilterChain(m_demux, m_decoders.at(i)); | |
} | |
for (int i = 0; i < list.count(); ++i) { | |
removeFilter(list.at(i)); | |
} | |
//Let's reinitialize the internal lists | |
m_decoderPins.clear(); | |
m_decoders.clear(); | |
m_demux = Filter(); | |
m_realSource = Filter(); | |
m_mediaSource = Phonon::MediaSource(); | |
absoluteSeek(0); //resets the clock | |
return S_OK; | |
} | |
bool MediaGraph::disconnectNodes(BackendNode *source, BackendNode *sink) | |
{ | |
const Filter sinkFilter = sink->filter(m_index); | |
const QList<InputPin> inputs = BackendNode::pins(sinkFilter, PINDIR_INPUT); | |
QList<OutputPin> outputs; | |
if (source == m_mediaObject) { | |
outputs = BackendNode::pins(m_fakeSource, PINDIR_OUTPUT); | |
outputs += m_decoderPins; | |
} else { | |
outputs = BackendNode::pins(source->filter(m_index), PINDIR_OUTPUT); | |
} | |
for (int i = 0; i < inputs.count(); ++i) { | |
for (int o = 0; o < outputs.count(); ++o) { | |
tryDisconnect(outputs.at(o), inputs.at(i)); | |
} | |
} | |
if (m_sinkConnections.removeOne(sink)) { | |
m_connectionsDirty = true; | |
} | |
return true; | |
} | |
bool MediaGraph::tryDisconnect(const OutputPin &out, const InputPin &in) | |
{ | |
bool ret = false; | |
OutputPin output; | |
if (SUCCEEDED(in->ConnectedTo(output.pparam()))) { | |
if (output == out) { | |
//we need a simple disconnection | |
ret = SUCCEEDED(out->Disconnect()) && SUCCEEDED(in->Disconnect()); | |
} else { | |
InputPin in2; | |
if (SUCCEEDED(out->ConnectedTo(in2.pparam()))) { | |
PIN_INFO info; | |
in2->QueryPinInfo(&info); | |
Filter tee(info.pFilter); | |
CLSID clsid; | |
tee->GetClassID(&clsid); | |
if (clsid == CLSID_InfTee) { | |
//we have to remove all intermediate filters between the tee and the sink | |
PIN_INFO info; | |
in->QueryPinInfo(&info); | |
Filter sink(info.pFilter); | |
QList<Filter> list = getFilterChain(tee, sink); | |
out->QueryPinInfo(&info); | |
Filter source(info.pFilter); | |
if (list.isEmpty()) { | |
output->QueryPinInfo(&info); | |
if (Filter(info.pFilter) == tee) { | |
ret = SUCCEEDED(output->Disconnect()) && SUCCEEDED(in->Disconnect()); | |
} | |
} else { | |
ret = true; | |
for (int i = 0; i < list.count(); ++i) { | |
ret = ret && SUCCEEDED(removeFilter(list.at(i))); | |
} | |
} | |
//Let's try to see if the Tee filter is still useful | |
if (ret) { | |
int connections = 0; | |
const QList<OutputPin> outputs = BackendNode::pins(tee, PINDIR_OUTPUT); | |
for(int i = 0; i < outputs.count(); ++i) { | |
InputPin p; | |
if ( SUCCEEDED(outputs.at(i)->ConnectedTo(p.pparam()))) { | |
connections++; | |
} | |
} | |
if (connections == 0) { | |
//this avoids a crash if the filter is destroyed | |
//by the subsequent call to removeFilter | |
output = OutputPin(); | |
removeFilter(tee); //there is no more output for the tee, we remove it | |
} | |
} | |
} | |
} | |
} | |
} | |
return ret; | |
} | |
bool MediaGraph::tryConnect(const OutputPin &out, const InputPin &newIn) | |
{ | |
///The management of the creation of the Tees is done here (this is the only place where we call IPin::Connect | |
InputPin inPin; | |
if (SUCCEEDED(out->ConnectedTo(inPin.pparam()))) { | |
//the fake source has another mechanism for the connection | |
if (BackendNode::pins(m_fakeSource, PINDIR_OUTPUT).contains(out)) { | |
return false; | |
} | |
//the output pin is already connected | |
PIN_INFO info; | |
inPin->QueryPinInfo(&info); | |
Filter filter(info.pFilter); //this will ensure the interface is "Release"d | |
CLSID clsid; | |
filter->GetClassID(&clsid); | |
if (clsid == CLSID_InfTee) { | |
//there is already a Tee (namely 'filter') in use | |
const QList<OutputPin> outputs = BackendNode::pins(filter, PINDIR_OUTPUT); | |
for(int i = 0; i < outputs.count(); ++i) { | |
const OutputPin &pin = outputs.at(i); | |
if (HRESULT(VFW_E_NOT_CONNECTED) == pin->ConnectedTo(inPin.pparam())) { | |
return SUCCEEDED(pin->Connect(newIn, 0)); | |
} | |
} | |
//we shoud never go here | |
return false; | |
} else { | |
QAMMediaType type; | |
out->ConnectionMediaType(&type); | |
//first we disconnect the current connection (and we save the current media type) | |
if (!tryDisconnect(out, inPin)) { | |
return false; | |
} | |
//..then we try to connect the new node | |
if (SUCCEEDED(out->Connect(newIn, 0))) { | |
//we have to insert the Tee | |
if (!tryDisconnect(out, newIn)) { | |
return false; | |
} | |
Filter filter(CLSID_InfTee, IID_IBaseFilter); | |
if (!filter) { | |
//rollback | |
m_graph->Connect(out, inPin); | |
return false; | |
} | |
if (FAILED(m_graph->AddFilter(filter, 0))) { | |
return false; | |
} | |
InputPin teeIn = BackendNode::pins(filter, PINDIR_INPUT).first(); //a Tee has always one input | |
HRESULT hr = out->Connect(teeIn, &type); | |
if (FAILED(hr)) { | |
hr = m_graph->Connect(out, teeIn); | |
} | |
if (FAILED(hr)) { | |
m_graph->Connect(out, inPin); | |
return false; | |
} | |
OutputPin teeOut = BackendNode::pins(filter, PINDIR_OUTPUT).last(); //the last is always the one that's not connected | |
//we simply reconnect the pins as they | |
hr = m_graph->Connect(teeOut, inPin); | |
if (FAILED(hr)) { | |
m_graph->Connect(out, inPin); | |
return false; | |
} | |
teeOut = BackendNode::pins(filter, PINDIR_OUTPUT).last(); //the last is always the one that's not connected | |
if (FAILED(m_graph->Connect(teeOut, newIn))) { | |
m_graph->Connect(out, inPin); | |
return false; | |
} | |
return true; | |
} else { | |
//we simply reconnect the pins as they | |
m_graph->Connect(out, inPin); | |
return false; | |
} | |
} | |
} else { | |
return SUCCEEDED(m_graph->Connect(out, newIn)); | |
} | |
} | |
bool MediaGraph::connectNodes(BackendNode *source, BackendNode *sink) | |
{ | |
bool ret = false; | |
const QList<InputPin> inputs = BackendNode::pins(sink->filter(m_index), PINDIR_INPUT); | |
QList<OutputPin> outputs = BackendNode::pins(source == m_mediaObject ? m_fakeSource : source->filter(m_index), PINDIR_OUTPUT); | |
if (source == m_mediaObject) { | |
grabFilter(m_fakeSource); | |
} | |
#ifdef GRAPH_DEBUG | |
qDebug() << Q_FUNC_INFO << source << sink << this; | |
#endif | |
for (int o = 0; o < outputs.count(); o++) { | |
InputPin p; | |
for (int i = 0; i < inputs.count(); i++) { | |
const InputPin &inPin = inputs.at(i); | |
if (tryConnect(outputs.at(o), inPin)) { | |
//tell the sink node that it just got a new input | |
sink->connected(source, inPin); | |
ret = true; | |
if (source == m_mediaObject) { | |
m_connectionsDirty = true; | |
m_sinkConnections += sink; | |
#ifdef GRAPH_DEBUG | |
qDebug() << "found a sink connection" << sink << m_sinkConnections.count(); | |
#endif | |
} | |
break; | |
} | |
} | |
} | |
return ret; | |
} | |
HRESULT MediaGraph::loadSource(const Phonon::MediaSource &source) | |
{ | |
m_hasVideo = false; | |
m_hasAudio = false; | |
m_isSeekable = false; | |
//cleanup of the previous filters | |
m_result = cleanup(); | |
if (FAILED(m_result)) { | |
return m_result; | |
} | |
m_mediaSource = source; | |
switch (source.type()) | |
{ | |
case Phonon::MediaSource::Disc: | |
if (source.discType() == Phonon::Dvd) { | |
m_result = E_NOTIMPL; | |
/*m_realSource = Filter(CLSID_DVDNavigator, IID_IBaseFilter); | |
if (m_realSource) { | |
return REGDB_E_CLASSNOTREG; | |
} | |
m_result = m_graph->AddFilter(m_realSource, L"DVD Navigator");*/ | |
#ifndef QT_NO_PHONON_MEDIACONTROLLER | |
} else if (source.discType() == Phonon::Cd) { | |
m_realSource = Filter(new QAudioCDPlayer); | |
#endif //QT_NO_PHONON_MEDIACONTROLLER | |
} else { | |
m_result = E_NOTIMPL; | |
} | |
if (FAILED(m_result)) { | |
return m_result; | |
} | |
m_renderId = m_mediaObject->workerThread()->addFilterToRender(m_realSource); | |
return m_result; | |
case Phonon::MediaSource::Invalid: | |
return m_result; | |
case Phonon::MediaSource::Url: | |
case Phonon::MediaSource::LocalFile: | |
{ | |
QString url; | |
if (source.type() == Phonon::MediaSource::LocalFile) { | |
url = source.fileName(); | |
} else { | |
url = source.url().toString(); | |
} | |
m_renderId = m_mediaObject->workerThread()->addUrlToRender(url); | |
} | |
break; | |
#ifndef QT_NO_PHONON_ABSTRACTMEDIASTREAM | |
case Phonon::MediaSource::Stream: | |
{ | |
m_realSource = Filter(new IODeviceReader(source, this)); | |
m_renderId = m_mediaObject->workerThread()->addFilterToRender(m_realSource); | |
} | |
break; | |
#endif //QT_NO_PHONON_ABSTRACTMEDIASTREAM | |
default: | |
m_result = E_FAIL; | |
} | |
return m_result; | |
} | |
void MediaGraph::finishSeeking(quint16 workId, qint64 time) | |
{ | |
if (m_seekId == workId) { | |
m_currentTime = time; | |
m_mediaObject->seekingFinished(this); | |
m_seekId = 0; | |
} else { | |
//it's a queue seek command | |
//we're still seeking | |
} | |
} | |
void MediaGraph::finishLoading(quint16 workId, HRESULT hr, Graph graph) | |
{ | |
if (m_renderId == workId) { | |
m_renderId = 0; | |
//let's determine if the graph is seekable | |
{ | |
ComPointer<IMediaSeeking> mediaSeeking(graph, IID_IMediaSeeking); | |
DWORD caps = AM_SEEKING_CanSeekAbsolute; | |
m_isSeekable = mediaSeeking && SUCCEEDED(mediaSeeking->CheckCapabilities(&caps)); | |
} | |
m_result = reallyFinishLoading(hr, graph); | |
m_mediaObject->loadingFinished(this); | |
} | |
} | |
HRESULT MediaGraph::reallyFinishLoading(HRESULT hr, const Graph &graph) | |
{ | |
if (FAILED(hr)) { | |
return hr; | |
} | |
const Graph oldGraph = m_graph; | |
m_graph = graph; | |
//we keep the source and all the way down to the decoders | |
QList<Filter> removedFilters; | |
const QList<Filter> allFilters = getAllFilters(graph); | |
for (int i = 0; i < allFilters.count(); ++i) { | |
const Filter &filter = allFilters.at(i); | |
if (isSourceFilter(filter)) { | |
m_realSource = filter; //save the source filter | |
if (!m_demux ) { | |
m_demux = filter; //in the WMV case, the demuxer is the source filter itself | |
} | |
} else if (isDemuxerFilter(filter)) { | |
m_demux = filter; | |
} else if (isDecoderFilter(filter)) { | |
m_decoders += filter; | |
m_decoderPins += BackendNode::pins(filter, PINDIR_OUTPUT).first(); | |
} else { | |
removedFilters += filter; | |
} | |
} | |
for (int i = 0; i < m_decoders.count(); ++i) { | |
QList<Filter> chain = getFilterChain(m_demux, m_decoders.at(i)); | |
for (int i = 0; i < chain.count(); ++i) { | |
//we keep those filters | |
removedFilters.removeOne(chain.at(i)); | |
} | |
} | |
for (int i = 0; i < removedFilters.count(); ++i) { | |
graph->RemoveFilter(removedFilters.at(i)); | |
} | |
m_mediaObject->workerThread()->replaceGraphForEventManagement(graph, oldGraph); | |
//let's transfer the nodes from the current graph to the new one | |
QList<GraphConnection> connections; //we store the connections that need to be restored | |
// First get all the sink nodes (nodes with no input connected) | |
for (int i = 0; i < m_sinkConnections.count(); ++i) { | |
Filter currentFilter = m_sinkConnections.at(i)->filter(m_index); | |
connections += getConnections(currentFilter); | |
grabFilter(currentFilter); | |
} | |
//we need to do something smart to detect if the streams are unencoded | |
if (m_demux) { | |
const QList<OutputPin> outputs = BackendNode::pins(m_demux, PINDIR_OUTPUT); | |
for (int i = 0; i < outputs.count(); ++i) { | |
const OutputPin &out = outputs.at(i); | |
InputPin pin; | |
if (out->ConnectedTo(pin.pparam()) == HRESULT(VFW_E_NOT_CONNECTED)) { | |
m_decoderPins += out; //unconnected outputs can be decoded outputs | |
} | |
} | |
} | |
ensureSourceConnectedTo(true); | |
//let's reestablish the connections | |
for (int i = 0; i < connections.count(); ++i) { | |
const GraphConnection &connection = connections.at(i); | |
//check if we shoud transfer the sink node | |
grabFilter(connection.input); | |
grabFilter(connection.output); | |
const OutputPin output = BackendNode::pins(connection.output, PINDIR_OUTPUT).at(connection.outputOffset); | |
const InputPin input = BackendNode::pins(connection.input, PINDIR_INPUT).at(connection.inputOffset); | |
HRESULT hr = output->Connect(input, 0); | |
Q_UNUSED(hr); | |
Q_ASSERT( SUCCEEDED(hr)); | |
} | |
//Finally, let's update the interfaces | |
m_mediaControl = ComPointer<IMediaControl>(graph, IID_IMediaControl); | |
m_mediaSeeking = ComPointer<IMediaSeeking>(graph, IID_IMediaSeeking); | |
return hr; | |
} | |
//utility functions | |
//retrieves the filters between source and sink | |
QList<Filter> MediaGraph::getFilterChain(const Filter &source, const Filter &sink) | |
{ | |
QList<Filter> ret; | |
Filter current = sink; | |
while (current && BackendNode::pins(current, PINDIR_INPUT).count() == 1 && current != source) { | |
if (current != source) | |
ret += current; | |
InputPin pin = BackendNode::pins(current, PINDIR_INPUT).first(); | |
current = Filter(); | |
OutputPin output; | |
if (pin->ConnectedTo(output.pparam()) == S_OK) { | |
PIN_INFO info; | |
if (SUCCEEDED(output->QueryPinInfo(&info)) && info.pFilter) { | |
current = Filter(info.pFilter); //this will take care of releasing the interface pFilter | |
} | |
} | |
} | |
if (current != source) { | |
//the soruce and sink don't seem to be connected | |
ret.clear(); | |
} | |
return ret; | |
} | |
bool MediaGraph::isDecoderFilter(const Filter &filter) | |
{ | |
if (filter == 0) { | |
return false; | |
} | |
#ifdef GRAPH_DEBUG | |
{ | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
qDebug() << Q_FUNC_INFO << QString((const QChar *)info.achName); | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
} | |
} | |
#endif | |
QList<InputPin> inputs = BackendNode::pins(filter, PINDIR_INPUT); | |
QList<OutputPin> outputs = BackendNode::pins(filter, PINDIR_OUTPUT); | |
//TODO: find a better way to detect if a node is a decoder | |
if (inputs.count() == 0 || outputs.count() ==0) { | |
return false; | |
} | |
//the input pin must be encoded data | |
QAMMediaType type; | |
HRESULT hr = inputs.first()->ConnectionMediaType(&type); | |
if (FAILED(hr)) { | |
return false; | |
} | |
//...and the output must be decoded | |
QAMMediaType type2; | |
hr = outputs.first()->ConnectionMediaType(&type2); | |
if (FAILED(hr)) { | |
return false; | |
} | |
if (type2.majortype != MEDIATYPE_Video && | |
type2.majortype != MEDIATYPE_Audio) { | |
return false; | |
} | |
if (type2.majortype == MEDIATYPE_Video) { | |
m_hasVideo = true; | |
} else { | |
m_hasAudio = true; | |
} | |
#ifdef GRAPH_DEBUG | |
{ | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
qDebug() << "found a decoder filter" << QString((const QChar *)info.achName); | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
} | |
} | |
#endif | |
return true; | |
} | |
bool MediaGraph::isSourceFilter(const Filter &filter) const | |
{ | |
#ifdef GRAPH_DEBUG | |
{ | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
qDebug() << Q_FUNC_INFO << QString((const QChar *)info.achName); | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
} | |
} | |
#endif | |
//a source filter is one that has no input | |
return BackendNode::pins(filter, PINDIR_INPUT).isEmpty(); | |
} | |
bool MediaGraph::isDemuxerFilter(const Filter &filter) const | |
{ | |
QList<InputPin> inputs = BackendNode::pins(filter, PINDIR_INPUT); | |
QList<OutputPin> outputs = BackendNode::pins(filter, PINDIR_OUTPUT); | |
#ifdef GRAPH_DEBUG | |
{ | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
qDebug() << Q_FUNC_INFO << QString((const QChar *)info.achName); | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
} | |
} | |
#endif | |
if (inputs.count() != 1 || outputs.count() == 0) { | |
return false; //a demuxer has only one input | |
} | |
QAMMediaType type; | |
HRESULT hr = inputs.first()->ConnectionMediaType(&type); | |
if (FAILED(hr)) { | |
return false; | |
} | |
if (type.majortype != MEDIATYPE_Stream) { | |
return false; | |
} | |
for (int i = 0; i < outputs.count(); ++i) { | |
QAMMediaType type; | |
//for now we support only video and audio | |
hr = outputs.at(i)->ConnectionMediaType(&type); | |
if (SUCCEEDED(hr) && | |
type.majortype != MEDIATYPE_Video && type.majortype != MEDIATYPE_Audio) { | |
return false; | |
} | |
} | |
#ifdef GRAPH_DEBUG | |
{ | |
FILTER_INFO info; | |
filter->QueryFilterInfo(&info); | |
qDebug() << "found a demuxer filter" << QString((const QChar *)info.achName); | |
if (info.pGraph) { | |
info.pGraph->Release(); | |
} | |
} | |
#endif | |
return true; | |
} | |
QMultiMap<QString, QString> MediaGraph::metadata() const | |
{ | |
QMultiMap<QString, QString> ret; | |
ComPointer<IAMMediaContent> mediaContent(m_demux, IID_IAMMediaContent); | |
if (mediaContent) { | |
//let's get the meta data | |
BSTR str; | |
HRESULT hr = mediaContent->get_AuthorName(&str); | |
if (SUCCEEDED(hr)) { | |
ret.insert(QLatin1String("ARTIST"), QString::fromWCharArray(str)); | |
SysFreeString(str); | |
} | |
hr = mediaContent->get_Title(&str); | |
if (SUCCEEDED(hr)) { | |
ret.insert(QLatin1String("TITLE"), QString::fromWCharArray(str)); | |
SysFreeString(str); | |
} | |
hr = mediaContent->get_Description(&str); | |
if (SUCCEEDED(hr)) { | |
ret.insert(QLatin1String("DESCRIPTION"), QString::fromWCharArray(str)); | |
SysFreeString(str); | |
} | |
hr = mediaContent->get_Copyright(&str); | |
if (SUCCEEDED(hr)) { | |
ret.insert(QLatin1String("COPYRIGHT"), QString::fromWCharArray(str)); | |
SysFreeString(str); | |
} | |
hr = mediaContent->get_MoreInfoText(&str); | |
if (SUCCEEDED(hr)) { | |
ret.insert(QLatin1String("MOREINFO"), QString::fromWCharArray(str)); | |
SysFreeString(str); | |
} | |
} | |
return ret; | |
} | |
Filter MediaGraph::realSource() const | |
{ | |
return m_realSource; | |
} | |
#ifndef QT_NO_PHONON_MEDIACONTROLLER | |
void MediaGraph::setStopPosition(qint64 time) | |
{ | |
qint64 current = 0, | |
stop = 0; | |
m_mediaSeeking->GetPositions(¤t, &stop); | |
const bool shouldSeek = current == stop; | |
if (time == -1) { | |
HRESULT hr = m_mediaSeeking->GetDuration(&time); | |
if (FAILED(hr)) { | |
return; | |
} | |
} else { | |
time *= 10000; | |
} | |
if (time == stop) { | |
//the stop position is already at the right place | |
return; | |
} | |
if (shouldSeek) { | |
m_mediaSeeking->SetPositions(¤t, AM_SEEKING_AbsolutePositioning, | |
&time, AM_SEEKING_AbsolutePositioning); | |
} else { | |
m_mediaSeeking->SetPositions(0, AM_SEEKING_NoPositioning, | |
&time, AM_SEEKING_AbsolutePositioning); | |
} | |
} | |
qint64 MediaGraph::stopPosition() const | |
{ | |
qint64 ret; | |
m_mediaSeeking->GetStopPosition(&ret); | |
return ret / 10000; | |
} | |
QList<qint64> MediaGraph::titles() const | |
{ | |
//for now we only manage that for the audio cd | |
ComPointer<ITitleInterface> titleIFace(m_realSource, IID_ITitleInterface); | |
if (titleIFace) { | |
return titleIFace->titles(); | |
} else { | |
// the default value: only one title that starts at position 0 | |
return QList<qint64>() << 0; | |
} | |
} | |
#endif //QT_NO_PHONON_MEDIACONTROLLER | |
} | |
} | |
QT_END_NAMESPACE |