1
/*  This file is part of the KDE project.
2
3
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
5
This library is free software: you can redistribute it and/or modify
6
it under the terms of the GNU Lesser General Public License as published by
7
the Free Software Foundation, either version 2.1 or 3 of the License.
8
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU Lesser General Public License for more details.
13
14
You should have received a copy of the GNU Lesser General Public License
15
along with this library.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
#include "backend.h"
19
#include "backendnode.h"
20
21
#include "audiooutput.h"
22
#include "effect.h"
23
#include "mediaobject.h"
24
#include "videowidget.h"
25
#include "volumeeffect.h"
26
27
//windows specific (DirectX Media Object)
28
#include <dmo.h>
29
30
#include <QtCore/QSettings>
31
#include <QtCore/QSet>
32
#include <QtCore/QVariant>
33
34
#include <QtCore/QtPlugin>
35
36
QT_BEGIN_NAMESPACE
37
38
Q_EXPORT_PLUGIN2(phonon_ds9, Phonon::DS9::Backend);
39
40
namespace Phonon
41
{
42
    namespace DS9
43
    {
44
        QMutex *Backend::directShowMutex = 0;
45
46
        bool Backend::AudioMoniker::operator==(const AudioMoniker &other)
47
        {
48
            return other->IsEqual(*this) == S_OK;
49
        }
50
51
52
        Backend::Backend(QObject *parent, const QVariantList &)
53
            : QObject(parent)
54
        {
55
            directShowMutex = &m_directShowMutex;
56
57
            ::CoInitialize(0);
58
59
            //registering meta types
60
            qRegisterMetaType<HRESULT>("HRESULT");
61
            qRegisterMetaType<Graph>("Graph");
62
        }
63
64
        Backend::~Backend()
65
        {
66
            m_audioOutputs.clear();
67
            m_audioEffects.clear();
68
            ::CoUninitialize();
69
70
            directShowMutex = 0;
71
        }
72
73
        QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const QList<QVariant> &args)
74
        {
75
            switch (c)
76
            {
77
            case MediaObjectClass:
78
                return new MediaObject(parent);
79
            case AudioOutputClass:
80
                return new AudioOutput(this, parent);
81
#ifndef QT_NO_PHONON_EFFECT
82
            case EffectClass:
83
                return new Effect(m_audioEffects[ args[0].toInt() ], parent);
84
#endif //QT_NO_PHONON_EFFECT
85
#ifndef QT_NO_PHONON_VIDEO
86
            case VideoWidgetClass:
87
                return new VideoWidget(qobject_cast<QWidget *>(parent));
88
#endif //QT_NO_PHONON_VIDEO
89
#ifndef QT_NO_PHONON_VOLUMEFADEREFFECT
90
            case VolumeFaderEffectClass:
91
                return new VolumeEffect(parent);
92
#endif //QT_NO_PHONON_VOLUMEFADEREFFECT
93
            default:
94
                return 0;
95
            }
96
        }
97
98
        bool Backend::supportsVideo() const
99
        {
100
#ifndef QT_NO_PHONON_VIDEO
101
            return true;
102
#else
103
            return false;
104
#endif //QT_NO_PHONON_VIDEO
105
        }
106
107
        QStringList Backend::availableMimeTypes() const
108
        {
109
            QStringList ret;
110
            {
111
                QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Multimedia\\mplayer2\\mime types"), QSettings::NativeFormat);
112
                ret += settings.childGroups();
113
            }
114
            {
115
                QSettings settings(QLatin1String("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Multimedia\\wmplayer\\mime types"), QSettings::NativeFormat);
116
                ret += settings.childGroups();
117
            }
118
119
            ret.removeDuplicates();
120
            ret.replaceInStrings("\\", "/");
121
            qSort(ret);
122
            return ret;
123
        }
124
125
		Filter Backend::getAudioOutputFilter(int index) const
126
		{
127
			Filter ret;
128
			if (index >= 0 && index < m_audioOutputs.count()) {
129
				m_audioOutputs.at(index)->BindToObject(0, 0, IID_IBaseFilter, reinterpret_cast<void**>(&ret));
130
			} else {
131
				//just return the default audio renderer (not directsound)
132
				ret = Filter(CLSID_AudioRender, IID_IBaseFilter);
133
			}
134
			return ret;
135
		}
136
137
138
        QList<int> Backend::objectDescriptionIndexes(Phonon::ObjectDescriptionType type) const
139
        {
140
            QMutexLocker locker(&m_directShowMutex);
141
            QList<int> ret;
142
143
            switch(type)
144
            {
145
            case Phonon::AudioOutputDeviceType:
146
                {
147
#ifdef Q_OS_WINCE
148
					ret << 0; // only one audio device with index 0
149
#else
150
					ComPointer<ICreateDevEnum> devEnum(CLSID_SystemDeviceEnum, IID_ICreateDevEnum);
151
					if (!devEnum) {
152
						return ret; //it is impossible to enumerate the devices
153
					}
154
                    ComPointer<IEnumMoniker> enumMon;
155
                    HRESULT hr = devEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, enumMon.pparam(), 0);
156
                    if (FAILED(hr)) {
157
                        break;
158
                    }
159
                    AudioMoniker mon;
160
161
                    //let's reorder the devices so that directshound appears first
162
                    int nbds = 0; //number of directsound devices
163
164
                    while (S_OK == enumMon->Next(1, mon.pparam(), 0)) {
165
                        LPOLESTR str = 0;
166
                        mon->GetDisplayName(0,0,&str);
167
                        const QString name = QString::fromWCharArray(str);
168
						ComPointer<IMalloc> alloc;
169
						::CoGetMalloc(1, alloc.pparam());
170
                        alloc->Free(str);
171
172
                        int insert_pos = 0;
173
                        if (!m_audioOutputs.contains(mon)) {
174
                            insert_pos = m_audioOutputs.count();
175
                            m_audioOutputs.append(mon);
176
                        } else {
177
                            insert_pos = m_audioOutputs.indexOf(mon);
178
                        }
179
180
                        if (name.contains(QLatin1String("DirectSound"))) {
181
                            ret.insert(nbds++, insert_pos);
182
                        } else {
183
                            ret.append(insert_pos);
184
                        }
185
                    }
186
#endif
187
					break;
188
                }
189
#ifndef QT_NO_PHONON_EFFECT
190
            case Phonon::EffectType:
191
                {
192
                    m_audioEffects.clear();
193
                    ComPointer<IEnumDMO> enumDMO;
194
                    HRESULT hr = ::DMOEnum(DMOCATEGORY_AUDIO_EFFECT, DMO_ENUMF_INCLUDE_KEYED, 0, 0, 0, 0, enumDMO.pparam());
195
                    if (SUCCEEDED(hr)) {
196
                        CLSID clsid;
197
                        while (S_OK == enumDMO->Next(1, &clsid, 0, 0)) {
198
                            ret += m_audioEffects.count();
199
                            m_audioEffects.append(clsid);
200
                        }
201
                    }
202
                    break;
203
                }
204
                break;
205
#endif //QT_NO_PHONON_EFFECT
206
            default:
207
                break;
208
            }
209
			return ret;
210
        }
211
212
        QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(Phonon::ObjectDescriptionType type, int index) const
213
        {
214
            QMutexLocker locker(&m_directShowMutex);
215
            QHash<QByteArray, QVariant> ret;
216
            switch (type)
217
            {
218
            case Phonon::AudioOutputDeviceType:
219
                {
220
#ifdef Q_OS_WINCE
221
					ret["name"] = QLatin1String("default audio device");
222
#else
223
                    const AudioMoniker &mon = m_audioOutputs[index];
224
                    LPOLESTR str = 0;
225
                    HRESULT hr = mon->GetDisplayName(0,0, &str);
226
                    if (SUCCEEDED(hr)) {
227
                        QString name = QString::fromWCharArray(str);
228
						ComPointer<IMalloc> alloc;
229
						::CoGetMalloc(1, alloc.pparam());
230
                        alloc->Free(str);
231
                        ret["name"] = name.mid(name.indexOf('\\') + 1);
232
					}
233
#endif
234
                }
235
                break;
236
#ifndef QT_NO_PHONON_EFFECT
237
            case Phonon::EffectType:
238
                {
239
                    WCHAR name[80]; // 80 is clearly stated in the MSDN doc
240
                    HRESULT hr = ::DMOGetName(m_audioEffects[index], name);
241
                    if (SUCCEEDED(hr)) {
242
                        ret["name"] = QString::fromWCharArray(name);
243
                    }
244
                }
245
                break;
246
#endif //QT_NO_PHONON_EFFECT
247
            default:
248
                break;
249
            }
250
			return ret;
251
        }
252
253
        bool Backend::endConnectionChange(QSet<QObject *> objects)
254
        {
255
            //end of a transaction
256
            for(QSet<QObject *>::const_iterator it = objects.begin(); it != objects.end(); ++it) {
257
                if (BackendNode *node = qobject_cast<BackendNode*>(*it)) {
258
                    MediaObject *mo = node->mediaObject();
259
                    if (mo) {
260
                        switch(mo->transactionState)
261
                        {
262
                        case Phonon::ErrorState:
263
                        case Phonon::StoppedState:
264
                        case Phonon::LoadingState:
265
                            //nothing to do
266
                            break;
267
                        case Phonon::PausedState:
268
                            mo->transactionState = Phonon::StoppedState;
269
                            mo->pause();
270
                            break;
271
                        default:
272
                            mo->transactionState = Phonon::StoppedState;
273
                            mo->play();
274
                            break;
275
                        }
276
277
                        if (mo->state() == Phonon::ErrorState)
278
                            return false;
279
                    }
280
                }
281
            }
282
283
            return true;
284
        }
285
286
287
        bool Backend::startConnectionChange(QSet<QObject *> objects)
288
        {
289
            //let's save the state of the graph (before we stop it)
290
            for(QSet<QObject *>::const_iterator it = objects.begin(); it != objects.end(); ++it) {
291
                if (BackendNode *node = qobject_cast<BackendNode*>(*it)) {
292
                    if (MediaObject *mo = node->mediaObject()) {
293
                        if (mo->state() != Phonon::StoppedState) {
294
                            mo->transactionState = mo->state();
295
                            mo->ensureStopped(); //we have to stop the graph..
296
                            if (mo->state() == Phonon::ErrorState)
297
                                return false;
298
                        }
299
                    }
300
                }
301
            }
302
303
            return true;
304
        }
305
306
        bool Backend::connectNodes(QObject *_source, QObject *_sink)
307
        {
308
            BackendNode *source = qobject_cast<BackendNode*>(_source);
309
            if (!source) {
310
                return false;
311
            }
312
            BackendNode *sink = qobject_cast<BackendNode*>(_sink);
313
            if (!sink) {
314
                return false;
315
            }
316
317
            //setting the graph if needed
318
            if (source->mediaObject() == 0 && sink->mediaObject() == 0) {
319
                    //error: no graph selected
320
                    return false;
321
            } else if (source->mediaObject() && source->mediaObject() != sink->mediaObject()) {
322
                //this' graph becomes the common one
323
                source->mediaObject()->grabNode(sink);
324
            } else if (source->mediaObject() == 0) {
325
                //sink's graph becomes the common one
326
                sink->mediaObject()->grabNode(source);
327
            }
328
329
            return source->mediaObject()->connectNodes(source, sink);
330
        }
331
332
        bool Backend::disconnectNodes(QObject *_source, QObject *_sink)
333
        {
334
            BackendNode *source = qobject_cast<BackendNode*>(_source);
335
            if (!source) {
336
                return false;
337
            }
338
            BackendNode *sink = qobject_cast<BackendNode*>(_sink);
339
            if (!sink) {
340
                return false;
341
            }
342
343
            return source->mediaObject() == 0 ||
344
                source->mediaObject()->disconnectNodes(source, sink);
345
        }
346
    }
347
}
348
349
QT_END_NAMESPACE
350
351
#include "moc_backend.cpp"