| /* 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 |