Qt for Android(十二) —— QT for Android QMediaPlayer JNI交互原理和源码分析

Qt for Android(十二) —— QT for Android QMediaPlayer JNI交互原理和源码分析这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战 背景   本文旨在对qml的mediaplayer和android的mediaplayer是怎样交互的,qml mediaplayer的p

这是我参与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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注