这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
背景
本文旨在对qml的mediaplayer和android的mediaplayer是怎样交互的,qml mediaplayer的play、pause等函数是这样向下传递的,和android mediaplayer的回调函数是怎样响应到qml的槽函数的进行简要的分析和梳理,以便于对这块的内容有一个大概的了解,并清楚原理。建议下载qt5.15的源码,用source insight阅读。
源码流程(QT端)
1、首先我们在QML中使用MediaPlayer组件,并设置了相关属性,增加了play、pause方法,覆盖了onplaying、onStatusChanged。如下代码:
MediaPlayer {
id: player
source: url
autoLoad: true
autoPlay: false
loops: 0
volume: ivolume
onPlaying: {
console.log("MediaPlayer onPlaying")
}
onError: {
if (MediaPlayer.NoError != error) {
console.log("[qmlvideo] error " + player.error
+ " errorString " + player.errorString)
}
}
onStatusChanged: {
console.log("onStatusChanged" + status)
}
}
function play() {
player.play()
}
QML 中的MediaPlayer其实就是QT中的QMediaPlayer(D:\WorkSoftware\Qt5.15.2\5.15.2\Src\qtmultimedia\src\multimedia\playback\qmediaplayer.cpp
),看下构造函数:
QMediaPlayer::QMediaPlayer(QObject *parent, QMediaPlayer::Flags flags):
QMediaObject(*new QMediaPlayerPrivate,
parent,
playerService(flags))
{
Q_D(QMediaPlayer);
d->provider = QMediaServiceProvider::defaultServiceProvider();
if (d->service == nullptr) {
d->error = ServiceMissingError;
} else {
d->control = qobject_cast<QMediaPlayerControl*>(d->service->requestControl(QMediaPlayerControl_iid));
#ifndef QT_NO_BEARERMANAGEMENT
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
d->networkAccessControl = qobject_cast<QMediaNetworkAccessControl*>(d->service->requestControl(QMediaNetworkAccessControl_iid));
QT_WARNING_POP
#endif
if (d->control != nullptr) {
connect(d->control, SIGNAL(mediaChanged(QMediaContent)), SLOT(_q_handleMediaChanged(QMediaContent)));
connect(d->control, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(_q_stateChanged(QMediaPlayer::State)));
connect(d->control, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
SLOT(_q_mediaStatusChanged(QMediaPlayer::MediaStatus)));
connect(d->control, SIGNAL(error(int,QString)), SLOT(_q_error(int,QString)));
connect(d->control, &QMediaPlayerControl::durationChanged, this, &QMediaPlayer::durationChanged);
connect(d->control, &QMediaPlayerControl::positionChanged, this, &QMediaPlayer::positionChanged);
connect(d->control, &QMediaPlayerControl::audioAvailableChanged, this, &QMediaPlayer::audioAvailableChanged);
connect(d->control, &QMediaPlayerControl::videoAvailableChanged, this, &QMediaPlayer::videoAvailableChanged);
connect(d->control, &QMediaPlayerControl::volumeChanged, this, &QMediaPlayer::volumeChanged);
connect(d->control, &QMediaPlayerControl::mutedChanged, this, &QMediaPlayer::mutedChanged);
connect(d->control, &QMediaPlayerControl::seekableChanged, this, &QMediaPlayer::seekableChanged);
connect(d->control, &QMediaPlayerControl::playbackRateChanged, this, &QMediaPlayer::playbackRateChanged);
connect(d->control, &QMediaPlayerControl::bufferStatusChanged, this, &QMediaPlayer::bufferStatusChanged);
d->state = d->control->state();
d->status = d->control->mediaStatus();
......
在这里我们只需要关注下d->control对象和其绑定的信号槽。此处调用了 d 指针的control对象,d 指针是QMediaPlayerPrivate对象,QT为了实现库的二进制兼容性,将类属性和方法实现通过私有指针的方式放到了源文件中,这样就屏蔽了实现细节和接口变动(具体可以google QT之d、p指针)。
2、然后qt到Android的流程跟踪以play方法为例。
void QMediaPlayer::play()
{
Q_D(QMediaPlayer);
if (d->control == nullptr) {
QMetaObject::invokeMethod(this, "_q_error", Qt::QueuedConnection,
Q_ARG(int, QMediaPlayer::ServiceMissingError),
Q_ARG(QString, tr("The QMediaPlayer object does not have a valid service")));
return;
}
......
d->control->play();
可以看到会调用这个d指针的control对象的play方法。所以我们先看下QMediaPlayerPrivate这个私有指针类:
class QMediaPlayerPrivate : public QMediaObjectPrivate
{
Q_DECLARE_NON_CONST_PUBLIC(QMediaPlayer)
public:
QMediaPlayerPrivate()
: provider(nullptr)
, control(nullptr)
, audioRoleControl(nullptr)
, customAudioRoleControl(nullptr)
, playlist(nullptr)
#ifndef QT_NO_BEARERMANAGEMENT
, networkAccessControl(nullptr)
#endif
, state(QMediaPlayer::StoppedState)
, status(QMediaPlayer::UnknownMediaStatus)
{}
QVideoSurfaceOutput surfaceOutput;
QMediaContent qrcMedia;
QScopedPointer<QFile> qrcFile;
QMediaContent rootMedia;
QMediaContent pendingPlaylist;
QMediaPlayer::State state;
QMediaPlayer::MediaStatus status;
QMediaPlayer::Error error;
int ignoreNextStatusChange;
int nestedPlaylists;
bool hasStreamPlaybackFeature;
QMediaPlaylist *parentPlaylist(QMediaPlaylist *pls);
bool isInChain(const QUrl &url);
void setMedia(const QMediaContent &media, QIODevice *stream = nullptr);
void setPlaylist(QMediaPlaylist *playlist);
void setPlaylistMedia();
void loadPlaylist();
void disconnectPlaylist();
void connectPlaylist();
void _q_stateChanged(QMediaPlayer::State state);
void _q_mediaStatusChanged(QMediaPlayer::MediaStatus status);
void _q_error(int error, const QString &errorString);
......
可以看到其中有个control对象,这便是我们上边看到的d->control。control类位于D:\WorkSoftware\Qt5.15.2\5.15.2\Src\qtmultimedia\src\plugins\android\src\mediaplayer\qandroidmediaplayercontrol.cpp
路径下,顾名思义,它的意思是Android MediaPlayer的控制类。看下构造函数,并跟踪下它的play方法:
QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent)
: QMediaPlayerControl(parent),
mMediaPlayer(new AndroidMediaPlayer),
mCurrentState(QMediaPlayer::StoppedState),
mCurrentMediaStatus(QMediaPlayer::NoMedia),
mMediaStream(0),
mVideoOutput(0),
mSeekable(true),
mBufferPercent(-1),
mBufferFilled(false),
mAudioAvailable(false),
mVideoAvailable(false),
mBuffering(false),
mState(AndroidMediaPlayer::Uninitialized),
mPendingState(-1),
mPendingPosition(-1),
mPendingSetMedia(false),
mPendingVolume(-1),
mPendingMute(-1),
mReloadingMedia(false),
mActiveStateChangeNotifiers(0),
mPendingPlaybackRate(1.0),
mHasPendingPlaybackRate(false)
{
connect(mMediaPlayer,SIGNAL(bufferingChanged(qint32)),
this,SLOT(onBufferingChanged(qint32)));
connect(mMediaPlayer,SIGNAL(info(qint32,qint32)),
this,SLOT(onInfo(qint32,qint32)));
connect(mMediaPlayer,SIGNAL(error(qint32,qint32)),
this,SLOT(onError(qint32,qint32)));
connect(mMediaPlayer,SIGNAL(stateChanged(qint32)),
this,SLOT(onStateChanged(qint32)));
connect(mMediaPlayer,SIGNAL(videoSizeChanged(qint32,qint32)),
...............................................
void QAndroidMediaPlayerControl::play()
{
StateChangeNotifier notifier(this);
// We need to prepare the mediaplayer again.
if ((mState & AndroidMediaPlayer::Stopped) && !mMediaContent.isNull()) {
setMedia(mMediaContent, mMediaStream);
}
if (!mMediaContent.isNull())
setState(QMediaPlayer::PlayingState);
if ((mState & (AndroidMediaPlayer::Prepared
| AndroidMediaPlayer::Started
| AndroidMediaPlayer::Paused
| AndroidMediaPlayer::PlaybackCompleted)) == 0) {
mPendingState = QMediaPlayer::PlayingState;
return;
}
mMediaPlayer->play();
}
上面的代码中,其一是绑定了一个AndroidMediaPlayer到Control类的信号槽,其二从setState
函数可以看到,在Control类中维护了一套AndroidMediaPlayer的播放状态。
这个类中还有个有趣的东西,即StateChangeNotifier notifier(this);
这句代码,其实就是借助了类的构造和退栈析构,在其中搞事情,可以顺便看下:
class StateChangeNotifier
{
public:
StateChangeNotifier(QAndroidMediaPlayerControl *mp)
: mControl(mp)
, mPreviousState(mp->state())
, mPreviousMediaStatus(mp->mediaStatus())
{
++mControl->mActiveStateChangeNotifiers;
}
~StateChangeNotifier()
{
if (--mControl->mActiveStateChangeNotifiers)
return;
if (mPreviousMediaStatus != mControl->mediaStatus())
Q_EMIT mControl->mediaStatusChanged(mControl->mediaStatus());
if (mPreviousState != mControl->state())
Q_EMIT mControl->stateChanged(mControl->state());
}
private:
QAndroidMediaPlayerControl *mControl;
QMediaPlayer::State mPreviousState;
QMediaPlayer::MediaStatus mPreviousMediaStatus;
};
他的意思是用析构时的state和构造时的state作比较,如果发生了改变就会在析构的时候调用Q_EMIT stateChanged
更新播放器的状态,这个信号在最开始的QMediaPlayer的构造函数中见过,最终状态的更新会发送到QMediaPlayer中,最终也就会发送到QML层。
3、继续来跟踪play方法,在control类中调用了mMediaPlayer->play();
,mMediaPlayer是一个AndroidMediaPlayer对象,所以进入到AndroidMediaPlayer类(位于D:\WorkSoftware\Qt5.15.2\5.15.2\Src\qtmultimedia\src\plugins\android\src\wrappers\jni\androidmediaplayer.cpp
路径下):
static const char QtAndroidMediaPlayerClassName[] = "org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer";
typedef QVector<AndroidMediaPlayer *> MediaPlayerList;
Q_GLOBAL_STATIC(MediaPlayerList, mediaPlayers)
Q_GLOBAL_STATIC(QReadWriteLock, rwLock)
QT_BEGIN_NAMESPACE
AndroidMediaPlayer::AndroidMediaPlayer()
: QObject()
{
QWriteLocker locker(rwLock);
auto context = QtAndroidPrivate::activity() ? QtAndroidPrivate::activity() : QtAndroidPrivate::service();
const jlong id = reinterpret_cast<jlong>(this);
mMediaPlayer = QJNIObjectPrivate(QtAndroidMediaPlayerClassName,
"(Landroid/content/Context;J)V",
context,
id);
mediaPlayers->append(this);
}
AndroidMediaPlayer::~AndroidMediaPlayer()
{
QWriteLocker locker(rwLock);
const int i = mediaPlayers->indexOf(this);
Q_ASSERT(i != -1);
mediaPlayers->remove(i);
}
void AndroidMediaPlayer::play()
{
mMediaPlayer.callMethod<void>("start");
}
static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
QReadLocker locker(rwLock);
const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id));
if (Q_UNLIKELY(i == -1))
return;
Q_EMIT (*mediaPlayers)[i]->stateChanged(state);
}
bool AndroidMediaPlayer::initJNI(JNIEnv *env)
{
jclass clazz = QJNIEnvironmentPrivate::findClass(QtAndroidMediaPlayerClassName,
env);
static const JNINativeMethod methods[] = {
{"onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative)},
{"onBufferingUpdateNative", "(IJ)V", reinterpret_cast<void *>(onBufferingUpdateNative)},
{"onProgressUpdateNative", "(IJ)V", reinterpret_cast<void *>(onProgressUpdateNative)},
{"onDurationChangedNative", "(IJ)V", reinterpret_cast<void *>(onDurationChangedNative)},
{"onInfoNative", "(IIJ)V", reinterpret_cast<void *>(onInfoNative)},
{"onVideoSizeChangedNative", "(IIJ)V", reinterpret_cast<void *>(onVideoSizeChangedNative)},
{"onStateChangedNative", "(IJ)V", reinterpret_cast<void *>(onStateChangedNative)}
};
if (clazz && env->RegisterNatives(clazz,
methods,
sizeof(methods) / sizeof(methods[0])) != JNI_OK) {
return false;
}
return true;
}
可以看到AndroidMediaPlayer类中也有一个mMediaPlayer对象,这是一个QJNIObjectPrivate对象,代表了一个JNI对象。从构造函数中可以看到它指的是org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer
这个Java类,一会儿再看QtAndroidMediaPlayer类。
第一:可以看到play方法的调用最终是通过JNI调用Java类的start函数。到这儿就知道了QT的play方法怎么调到Android端。
第二:从上面第二行和第三行先看下这个静态的MediaPlayerList,它将AndroidMediaPlayer指针缓存到一个vector中。以onStateChangedNative函数为例,这是一个native函数,会被Java层调用。当收到Android端的onStateChanged时触发。所以上面构造函数中的ID会先传递到Android层,然后在收到native调用后再传回来,根据ID找到对应的AndroidMediaPlayer对象。结合上面第2步末尾的信号槽和这儿的信号槽也就知道了Android端的回调怎么传递到QT中乃至于QML中。
源码流程(Android端)
Android端的代码时位于jar包中的,位于D:\WorkSoftware\Qt5.15.2\5.15.2\android\jar
路径下。
简单看下它的成员对象和一些重要的方法:
public class QtAndroidMediaPlayer
{
// Native callback functions for MediaPlayer
native public void onErrorNative(int what, int extra, long id);
native public void onBufferingUpdateNative(int percent, long id);
native public void onProgressUpdateNative(int progress, long id);
native public void onDurationChangedNative(int duration, long id);
native public void onInfoNative(int what, int extra, long id);
native public void onVideoSizeChangedNative(int width, int height, long id);
native public void onStateChangedNative(int state, long id);
private MediaPlayer mMediaPlayer = null;
private AudioAttributes mAudioAttributes = null;
private HashMap<String, String> mHeaders = null;
private Uri mUri = null;
private final long mID;
public QtAndroidMediaPlayer(final Context context, final long id)
{
mID = id;
mContext = context;
}
private class MediaPlayerCompletionListener
implements MediaPlayer.OnCompletionListener
{
@Override
public void onCompletion(final MediaPlayer mp)
{
Log.d( TAG,"onCompletion mMediaPlayer.release()");
setState(org.qtproject.qt5.android.multimedia.QtAndroidMediaPlayer.State.PlaybackCompleted);
}
}
private void setState(int state)
{
if (mState == state)
return;
mState = state;
onStateChangedNative(mState, mID);
}
public void start()
{
if ((mState & (org.qtproject.qt5.android.multimedia.QtAndroidMediaPlayer.State.Prepared
| org.qtproject.qt5.android.multimedia.QtAndroidMediaPlayer.State.Started
| org.qtproject.qt5.android.multimedia.QtAndroidMediaPlayer.State.Paused
| org.qtproject.qt5.android.multimedia.QtAndroidMediaPlayer.State.PlaybackCompleted)) == 0) {
return;
}
try {
mMediaPlayer.start()
setState(org.qtproject.qt5.android.multimedia.QtAndroidMediaPlayer.State.Started);
Log.d(TAG, "start");
} catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage());
}
}
从上面的代码中我们能看到几个东西:
第一:如上面说的QT端和Android端均维护了一套状态。
第二:执行完start方法后会setState,setState会调用native函数onStateChangedNative,其实基本上每个函数都会setState,一是更新这边的状态,二是调用QT库的native函数,更新Control类里面的状态。
正常来说,jar包的代码是不允许更改的,但是为了一些定制功能,我们也可以去修改。但是此时我们不需要改jar包,因QT为源码中包含了这个jar包代码,在\5.15.2\Src\qtmultimedia\src\plugins\android\jar\src\org\qtproject\qt5\android\multimedia
这个路径下,可以直接拿过来加到项目中使用。
今天的文章Qt for Android(十二) —— QT for Android QMediaPlayer JNI交互原理和源码分析分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/17951.html