经过分析Qt Multimedia的QMediaPlayer播放器源码,发现了Qt是如何加载那些解码插件的。如果要实现自己的解码插件,让QMediaPlayer自动加载自己开发的解码插件,那么某些音视频文件在没有安装解码器的系统上也能正常播放。
首先,看看QMediaPlayer是如何工作的。以UML序列图表示:
QMediaPlayer调用的是QMediaPlayerControl的接口,其中QMediaPlayerControl是一个抽象类,所有接口需要自己实现。
QMediaServiceProviderPlugin是一个Qt插件,派生自QMediaServiceProviderFactoryInterface抽象接口,从它派生可生成一个“.dll”或“.a”类型的插件。然后发现,QMediaServiceProviderPlugin和QMediaService其实就是一个抽象工厂模式。QMediaService 的工作就是请求一系列不同功能的播放控制插件(解码、声音输出、声音输入等),如QAudioOutputControl、QAodioInputControl、QAudioDecoderControl、QMetaDataReaderControl、QMetaDataWriterControl等等。
1.QMediaPlayer
调用了QMediaServiceProvider的抽象接口requestService()。派生QPluginServiceProvider并实现了QMediaServiceProvider的接口。在defaultServiceProvider()函数中实例化一个QPluginServiceProvider对象。
//QMediaPlayer构造函数中调用playerService()
static QMediaService *playerService(QMediaPlayer::Flags flags)
{
QMediaServiceProvider *provider = QMediaServiceProvider::defaultServiceProvider();
if (flags) {
QMediaServiceProviderHint::Features features = 0;
if (flags & QMediaPlayer::LowLatency)
features |= QMediaServiceProviderHint::LowLatencyPlayback;
if (flags & QMediaPlayer::StreamPlayback)
features |= QMediaServiceProviderHint::StreamPlayback;
if (flags & QMediaPlayer::VideoSurface)
features |= QMediaServiceProviderHint::VideoSurface;
return provider->requestService(Q_MEDIASERVICE_MEDIAPLAYER,
QMediaServiceProviderHint(features));
} else
return provider->requestService(Q_MEDIASERVICE_MEDIAPLAYER);
}
2.QMediaServiceProvider
派生的子类QPluginServiceProvider实现了requestService函数。
class QPluginServiceProvider : public QMediaServiceProvider
{
//...
QMediaService* requestService(const QByteArray &type, const QMediaServiceProviderHint &hint)
{
QString key(QLatin1String(type.constData()));
QListplugins;
const auto instances = loader()->instances(key);
for (QObject *obj : instances) {
QMediaServiceProviderPlugin *plugin =
qobject_cast(obj);
if (plugin)
plugins << plugin;
}
//...
代码片段中的loader()是:
Q_GLOBAL_STATIC_WITH_ARGS(QMediaPluginLoader, loader, (QMediaServiceProviderFactoryInterface_iid, QLatin1String("mediaservice"), Qt::CaseInsensitive))
mediaservice是Qt加载多媒体插件的路径,即QT_DIR/plugins/mediaservice
3.QMediaPluginLoader
调用instances()成员函数。
// QFactoryLoader m_factoryLoader;
QList QMediaPluginLoader::instances(QString const &key)
{
if (!m_metadata.contains(key))
return QList();
QList objects;
const auto list = m_metadata.value(key);
for (const QJsonObject &jsonobj : list) {
int idx = jsonobj.value(QStringLiteral("index")).toDouble();
if (idx < 0)
continue;
QObject *object = m_factoryLoader->instance(idx);
if (!objects.contains(object)) {
objects.append(object);
}
}
return objects;
}
这里检查重复加载以及验证插件合法性,key是插件接口中的IID:
Q_PLUGIN_METADATA(IID “org.qt-project.qt.mediaserviceproviderfactory/5.0” FILE “wmf.json”)
4.QFactoryLoader
该类是Qt base的类,调用成员函数instance()。QLibraryPrivate是QLibrary的d指针。
QObject *QFactoryLoader::instance(int index) const
{
Q_D(const QFactoryLoader);
if (index < 0)
return 0;
#ifndef QT_NO_LIBRARY
if (index < d->libraryList.size()) {
QLibraryPrivate *library = d->libraryList.at(index);
if (library->instance || library->loadPlugin()) {
if (!library->inst)
library->inst = library->instance();
QObject *obj = library->inst.data();
if (obj) {
if (!obj->parent())
obj->moveToThread(QCoreApplicationPrivate::mainThread());
return obj;
}
}
return 0;
}
index -= d->libraryList.size();
#endif
// ...
}
5.QLibrary
调用loadPlugin()函数从本地(QT_DIR/plugins/mediaservice)加载dll插件并instance()实例化插件。
分析先到这,下次更新一篇如何实现一个基于FFmpeg实现的Qt插件。