blob: 3e7a68bac87aca0806e69558afbc456530a82920 [file] [log] [blame]
/* 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(&current, &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(&current, 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