알쓸전컴(알아두면 쓸모있는 전자 컴퓨터)

비디오 위젯 예제 분석 데이터의 흐름을 파악(2) 본문

QT/Qt Multimedia 공부하기

비디오 위젯 예제 분석 데이터의 흐름을 파악(2)

백곳 2017. 9. 16. 14:12

비디오 위젯 예제 분석 데이터의 흐름을 파악(2)


지난번 

mediaPlayer.play(); 가 동영상을 재생하는 과정 까지 어떻게 데이터가 전달되어 동영상으로 재생 되는지 알아 보겠습니다. 


이번에는 

\Src\qtmultimedia\src\multimedia 의 multimedia.pro 프로젝트를 열어서 play() 메소드를 찾아야 합니다. 


Src\qtmultimedia\src\multimedia\playback\qmediaplayer.cpp 을 보겠습니다. 

qmediaplayer.cpp

void QMediaPlayer::play()
{
    Q_D(QMediaPlayer);

    if (d->control == 0) {
        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;
    }

    //if playlist control is available, the service should advance itself
    if (d->rootMedia.playlist() && d->rootMedia.playlist()->currentIndex() == -1 && !d->rootMedia.playlist()->isEmpty()) {

        // switch to playing state
        if (d->state != QMediaPlayer::PlayingState)
            d->_q_stateChanged(QMediaPlayer::PlayingState);

        if (d->playlist != d->rootMedia.playlist())
            d->setPlaylist(d->rootMedia.playlist());
        Q_ASSERT(d->playlist == d->rootMedia.playlist());
        emit currentMediaChanged(d->rootMedia);
        d->playlist->setCurrentIndex(0);
    }

    // Reset error conditions
    d->error = NoError;
    d->errorString = QString();

    d->control->play();
}


여기서 Q_D 는 매크로 입니다. 

class QMediaPlayerPrivate;


#define Q_D(Class) Class##Private * const d = d_func()


QMediaPlayer 이름에 Private 라고 붙은 클래스에 접근 할수 있게 해주는 매크로 입니다. 

    if (d->control == 0) {
        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 객체가 생성 되었는지 체크 하여 없다면 "_q_error"에러를 발생할때  Q_ARG() 는 에러 발생에 데이터를 전달해 주는것입니다. 

에러때 Q_ARG에 있는 데이터를 전달해줘 입니다. _q_error 에러에 내용을 추가 하는 것입니다. 

    //if playlist control is available, the service should advance itself
    if (d->rootMedia.playlist() && d->rootMedia.playlist()->currentIndex() == -1 && !d->rootMedia.playlist()->isEmpty()) {

        // switch to playing state
        if (d->state != QMediaPlayer::PlayingState)
            d->_q_stateChanged(QMediaPlayer::PlayingState);

        if (d->playlist != d->rootMedia.playlist())
            d->setPlaylist(d->rootMedia.playlist());
        Q_ASSERT(d->playlist == d->rootMedia.playlist());
        emit currentMediaChanged(d->rootMedia);
        d->playlist->setCurrentIndex(0);
    }


d->rootMedia 에 플레이 리스트가 있는지 체크 합니다. 


그리고 플레이 리스트가 있으면 QMediaPlayerPrivate 의 데이터의 상태변경 함수를 실행 합니다. 

그리고 상태가 바뀌었다고 signal 을 보냅니다. 


d->rootMedia 뭘까요 ? 



qmediaplayer.cpp

class QMediaPlayerPrivate : public QMediaObjectPrivate
{
    Q_DECLARE_NON_CONST_PUBLIC(QMediaPlayer)

public:
    QMediaPlayerPrivate()
        : provider(0)
        , control(0)
        , audioRoleControl(0)
        , state(QMediaPlayer::StoppedState)
        , status(QMediaPlayer::UnknownMediaStatus)
        , error(QMediaPlayer::NoError)
        , ignoreNextStatusChange(-1)
        , playlist(0)
        , networkAccessControl(0)
        , hasStreamPlaybackFeature(false)
        , nestedPlaylists(0)
    {}

    QMediaServiceProvider *provider;
    QMediaPlayerControl* control;
    QAudioRoleControl *audioRoleControl;
    QMediaPlayer::State state;
    QMediaPlayer::MediaStatus status;
    QMediaPlayer::Error error;
    QString errorString;
    int ignoreNextStatusChange;

    QPointer<qobject> videoOutput;
    QMediaPlaylist *playlist;
    QMediaNetworkAccessControl *networkAccessControl;
    QVideoSurfaceOutput surfaceOutput;
    bool hasStreamPlaybackFeature;
    QMediaContent qrcMedia;
    QScopedPointer<qfile> qrcFile;

    QMediaContent rootMedia;
    QMediaContent pendingPlaylist;
    QMediaPlaylist *parentPlaylist(QMediaPlaylist *pls);
    bool isInChain(QUrl url);
    int nestedPlaylists;

    void setMedia(const QMediaContent &media, QIODevice *stream = 0);

    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);
    void _q_updateMedia(const QMediaContent&);
    void _q_playlistDestroyed();
    void _q_handleMediaChanged(const QMediaContent&);
    void _q_handlePlaylistLoaded();
    void _q_handlePlaylistLoadFailed();
};


void QMediaPlayer::setMedia(const QMediaContent &media, QIODevice *stream)
{
    Q_D(QMediaPlayer);
    stop();

    QMediaContent oldMedia = d->rootMedia;
    d->disconnectPlaylist();
    d->playlist = 0;
    d->rootMedia = media;
    d->nestedPlaylists = 0;

    if (oldMedia != media)
        emit mediaChanged(d->rootMedia);

    if (media.playlist()) {
        // reset playlist to the 1st item
        media.playlist()->setCurrentIndex(0);
        d->setPlaylist(media.playlist());
    } else {
        d->setMedia(media, stream);
    }
}


d->rootMedia = media; 로 데이터가 넘어 갑니다 .

그리고 d->setMedia(media, stream); 을 실행 시켜 

private 메소드에 넘겨 줍니다.


void QMediaPlayerPrivate::setMedia(const QMediaContent &media, QIODevice *stream)
{
    Q_Q(QMediaPlayer);

    if (!control)
        return;

    QScopedPointer<qfile> file;

    // Backends can't play qrc files directly.
    // If the backend supports StreamPlayback, we pass a QFile for that resource.
    // If it doesn't, we copy the data to a temporary file and pass its path.
    if (!media.isNull() && !stream && media.canonicalUrl().scheme() == QLatin1String("qrc")) {
        qrcMedia = media;

        file.reset(new QFile(QLatin1Char(':') + media.canonicalUrl().path()));
        if (!file->open(QFile::ReadOnly)) {
            QMetaObject::invokeMethod(q, "_q_error", Qt::QueuedConnection,
                                      Q_ARG(int, QMediaPlayer::ResourceError),
                                      Q_ARG(QString, QMediaPlayer::tr("Attempting to play invalid Qt resource")));
            QMetaObject::invokeMethod(q, "_q_mediaStatusChanged", Qt::QueuedConnection,
                                      Q_ARG(QMediaPlayer::MediaStatus, QMediaPlayer::InvalidMedia));
            file.reset();
            // Ignore the next NoMedia status change, we just want to clear the current media
            // on the backend side since we can't load the new one and we want to be in the
            // InvalidMedia status.
            ignoreNextStatusChange = QMediaPlayer::NoMedia;
            control->setMedia(QMediaContent(), 0);

        } else if (hasStreamPlaybackFeature) {
            control->setMedia(media, file.data());
        } else {
            QTemporaryFile *tempFile = new QTemporaryFile;

            // Preserve original file extension, some backends might not load the file if it doesn't
            // have an extension.
            const QString suffix = QFileInfo(*file).suffix();
            if (!suffix.isEmpty())
                tempFile->setFileTemplate(tempFile->fileTemplate() + QLatin1Char('.') + suffix);

            // Copy the qrc data into the temporary file
            tempFile->open();
            char buffer[4096];
            while (true) {
                qint64 len = file->read(buffer, sizeof(buffer));
                if (len < 1)
                    break;
                tempFile->write(buffer, len);
            }
            tempFile->close();

            file.reset(tempFile);
            control->setMedia(QMediaContent(QUrl::fromLocalFile(file->fileName())), 0);
        }
    } else {
        qrcMedia = QMediaContent();
        control->setMedia(media, stream);
    }

    qrcFile.swap(file); // Cleans up any previous file
}


56,57,60 번줄로 이동 하게 되는데 


57번째줄의 


QMediaPlayerControl* control;


virtual void setMedia(const QMediaContent &media, QIODevice *stream) = 0;

은 추상 함수로 오버라이딩을 해서 쓰는것이기 때문에 

현재는 다른 클래스에서 오버라이딩이 된것이 없기 때문에 사용 되는것이 없습니다. 


QScopedPointer qrcFile;  메모리 관리 클래스로 파일을 바꿔 줍니다. 




QMediaPlayer::setMedia 의 Qt 설명을 보겠습니다. 

[slot] void QMediaPlayer::setMedia(const QMediaContent &media, QIODevice *stream = Q_NULLPTR)


Sets the current media source

미디어로 가장 최근에 보여줄 데이터 소스를 셋팅하는곳 


If a stream is supplied; media data will be read from it instead of resolving the media source. In this case the media source may still be used to resolve additional information about the media such as mime type.

스트림이 제공되는 경우 미디어 원본을 해결하는 대신 미디어 데이터가 읽힙니다. 이 경우 미디어 원본은 여전히 마임 형식과 같은 미디어에 대한 추가 정보를 확인하는 데 사용될 수 있습니다.


Setting the media to a null QMediaContent will cause the player to discard all information relating to the current media source and to cease all I/O operations related to that media.

QMediaContent를 null로 설정한 미디어를 설정하면 플레이어가 현재 미디어 원본과 관련된 모든 정보를 삭제하고 해당 미디어와 관련된 모든 I/O작업을 중지하게 됩니다.


This function returns immediately after recording the specified source of the media.

이 기능은 매체의 지정된 소스를 녹화한 직후에 반환됩니다.


It does not wait for the media to finish loading and does not check for errors

미디어가 로드를 마치는 동안 기다리지 않고 오류를 확인하지 않습니다.


Listen for the mediaStatusChanged() and error() signals to be notified when the media is loaded and when an error occurs during loading.

미디어가 로드되고 로드 중에 오류가 발생할 때  mediaStatusChanged()및 error()신호가 수신될 때 알림을 수신합니다.




customvideowidget.pro 프로젝트를 보면 


videoplayer.cpp

void VideoPlayer::openFile()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open Movie"),QDir::homePath());

    if (!fileName.isEmpty()) {
        mediaPlayer.setMedia(QUrl::fromLocalFile(fileName));

        playButton->setEnabled(true);
    }
}


여기서 setMedia 에서 계속 넘겨주는 QMediaContent가 뭔지 좀더 알아 보겠습니다.


QMediaContent Class


Detailed Description

The QMediaContent class provides access to the resources relating to a media content

QMediaContent 클래스는 미디어 컨텐츠와 관련된 리소스에 대한 액세스 권한을 제공합니다.


QMediaContent is used within the multimedia framework as the logical handle to media content.

QMediaContent는 멀티 미디어 프레임워크 내에서 미디어 콘텐츠에 대한 논리적 처리로 사용됩니다.


 A QMediaContent object is composed of one or more QMediaResources where each resource provides the URL and format information of a different encoding of the content.

QMediaContent개체는 각 리소스가 서로 다른 인코딩의 URL과 포맷 정보를 제공하는 하나 이상의 QMediaResources로 

구성됩니다.


A non-null QMediaContent will always have a primary or canonical reference to the content available through the canonicalUrl() or canonicalResource() methods, any additional resources are optional.


Alternatively MediaContent can represent a playlist and contain a pointer to a valid MediaPlaylist object. In this case URL is optional and can either be empty or point to the playlist URL.

MediaContent는 재생 목록을 나타내고 올바른 MediaPlayllays개체에 대한 포인터를 포함할 수 있습니다. 이 경우 URL은 선택 사항이므로 비어 있거나 재생 목록 URL을 가리킬 수 있습니다


위 예제 파일에서 videoplayer.cpp 에서 mediaPlayer.setMedia(QUrl::fromLocalFile(fileName)); 는 


void QMediaPlayer::setMedia(const QMediaContent &media, QIODevice *stream) 를 사용 하는데 


const QMediaContent &mediaQMediaContent(QUrl::fromLocalFile(fileName)) 으로 변환 되어 넘겨 주면서 


생성자에서 초기화를 하게 됩니다. 


생성자

QMediaContent::QMediaContent(const QUrl &url)

Constructs a media content with url providing a reference to the content.

이것을 사용합니다. 


multimedia 프로젝트에서 


qmediacontent.cpp

QMediaContent::QMediaContent(const QUrl &url):
    d(new QMediaContentPrivate)
{
    d->resources << QMediaResource(url);
}


class QMediaContentPrivate : public QSharedData
{
public:
    QMediaContentPrivate():
        isPlaylistOwned(false)
    {}

    QMediaContentPrivate(const QMediaResourceList &r):
        resources(r),
        isPlaylistOwned(false)
    {}

    QMediaContentPrivate(const QMediaContentPrivate &other):
        QSharedData(other),
        resources(other.resources),
        playlist(other.playlist),
        isPlaylistOwned(false)
    {}

    QMediaContentPrivate(QMediaPlaylist *pls, const QUrl &url, bool isOwn):
        playlist(pls),
        isPlaylistOwned(isOwn)
    {
        resources << QMediaResource(url);
    }

    ~QMediaContentPrivate()
    {
        if (isPlaylistOwned && !playlist.isNull())
            playlist.data()->deleteLater();
    }

    bool operator ==(const QMediaContentPrivate &other) const
    {
        return resources == other.resources && playlist == other.playlist;
    }

    QMediaResourceList resources;

    QPointer<qmediaplaylist> playlist;
    bool isPlaylistOwned;
private:
    QMediaContentPrivate& operator=(const QMediaContentPrivate &other);
};


QMediaResourceList resources;


에 추가를 해줍니다 

QMediaResource 가 뭔지 알아 보겠습니다. 

QMediaResource Class
Detailed Description

The QMediaResource class provides a description of a media resource.
QMediaResource 클래스는 미디어 리소스에 대한 설명을 제공합니다.

A media resource is composed of a URL containing the location of the resource and a set of properties that describe the format of the resource.
미디어 리소스는 리소스의 위치를 설명하는 리소스의 위치와 리소스 집합을 포함하는 URL로 구성됩니다.

The properties provide a means to assess a resource without first attempting to load it, and in situations where media be represented by multiple alternative representations provide a means to select the appropriate resource.
속성을 사용하면 먼저 리소스를 로드하지 않고 리소스를 평가할 수 있으며, 여러 대체 표현을 사용하여 미디어를 표시하는 경우 적절한 리소스를 선택할 수 있습니다.

Media made available by a remote services can often be available in multiple encodings or quality levels, this allows a client to select an appropriate resource based on considerations such as codecs supported, network bandwidth, and display constraints
원격 서비스에서 사용할 수 있는 미디어는 종종 여러 인코딩 또는 품질 수준에서 사용할 수 있습니다. 이는 클라이언트가 지원되는 코덱, 네트워크 대역 폭 및 디스플레이 제약 조건과 같은 고려 사항에 따라 적절한 리소스를 선택할 수 있습니다.

QMediaResource includes information such as the MIME type, audio and video codecs, audio and video bit rates, and resolution so these constraints and others can be evaluated.

QMediaResource는 MIME유형, 오디오 및 비디오 코덱, 오디오 및 비디오 비트 전송률과 같은 정보를 비롯해 이러한 제약 조건을 평가할 수 있습니다.


The only mandatory property of a QMediaResource is the url().

QMediaResource 의 유일한 필수 속성은 URL()입니다.


설명에 나온 MIME 는 뭘까요 ?? 


QMimeType Class

Detailed Description

The QMimeType class describes types of file or data, represented by a MIME type string.

MimeType클래스는 MIME형식 문자열로 표현되는 파일 또는 데이터 형식을 설명합니다.


For instance a file named "readme.txt" has the MIME type "text/plain".

"readme.txt 은 "text/plain" 타입으로 알려 줍니다. 

The MIME type can be determined from the file name, or from the file contents, or from both

MIME 은 파일 형식과 파일 내용은 모두 포함되어 볼수 있습니다. 


MIME type determination can also be done on buffers of data not coming from files.

MIME유형 결정은 파일에서 나오지 않는 데이터 버퍼에서도 수행할 수 있습니다.


Determining the MIME type of a file can be useful to make sure your application supports it. It is also useful in file-manager-like applications or widgets, in order to display an appropriate icon for the file, or even the descriptive comment in detailed views.

파일의 MIME형식을 결정하는 것은 응용 프로그램이 지원하는지 확인하는 데 유용할 수 있습니다. 파일 관리자와 마찬가지로 파일 또는 위젯에 적합한 아이콘 또는 상세 보기에 대한 설명을 표시하기 위해 파일 관리자와 같은 유용한 기능도 사용할 수 있습니다.


결국에 데이터의 형식을 알려주는 클래스 이네요 .


다시 QMediaResource 로 돌아 와서 


qmediaresource.h

// Class forward declaration required for QDoc bug
class QString;
class Q_MULTIMEDIA_EXPORT QMediaResource
{
public:
    QMediaResource();
    QMediaResource(const QUrl &url, const QString &mimeType = QString());
    QMediaResource(const QNetworkRequest &request, const QString &mimeType = QString());
    QMediaResource(const QMediaResource &other);
    QMediaResource &operator =(const QMediaResource &other);
    ~QMediaResource();

    bool isNull() const;

    bool operator ==(const QMediaResource &other) const;
    bool operator !=(const QMediaResource &other) const;

    QUrl url() const;
    QNetworkRequest request() const;
    QString mimeType() const;

    QString language() const;
    void setLanguage(const QString &language);

    QString audioCodec() const;
    void setAudioCodec(const QString &codec);

    QString videoCodec() const;
    void setVideoCodec(const QString &codec);

    qint64 dataSize() const;
    void setDataSize(const qint64 size);

    int audioBitRate() const;
    void setAudioBitRate(int rate);

    int sampleRate() const;
    void setSampleRate(int frequency);

    int channelCount() const;
    void setChannelCount(int channels);

    int videoBitRate() const;
    void setVideoBitRate(int rate);

    QSize resolution() const;
    void setResolution(const QSize &resolution);
    void setResolution(int width, int height);


private:
    enum Property
    {
        Url,
        Request,
        MimeType,
        Language,
        AudioCodec,
        VideoCodec,
        DataSize,
        AudioBitRate,
        VideoBitRate,
        SampleRate,
        ChannelCount,
        Resolution
    };
    QMap<int, qvariant=""> values;
};

typedef QList<qmediaresource> QMediaResourceList;

qmediaresource.cpp

QMediaResource::QMediaResource(const QUrl &url, const QString &mimeType)
{
    values.insert(Url, url);
    values.insert(MimeType, mimeType);
}


결국 qmediaresource 은 미디어 play에 대한 설정을 저장하는 클래스 였네요 .



여기서는 setmedia 에 대해서 좀더 디테일하게 알아 보았습니다. 


결론은 class QMediaPlayerPrivate 의 control 객체가 play() 할때 사용할 정보를 넘겨주는것이 였네요 .


지금까지는 비디오 파일에 속성 데이터가 어떻게 전해지는지만 보았고 다음 분석때 play에 대해서 좀더 자세히 파악해야 할듯 합니다. 



'QT > Qt Multimedia 공부하기' 카테고리의 다른 글

QMediaPlayer(1)  (0) 2017.09.19
QMediaObject  (0) 2017.09.18
비디오 위젯 예제 분석 데이터의 흐름을 파악(1)  (0) 2017.09.16
[Qt Multimedia]Multimedia Recipes  (0) 2017.09.15
[Qt Multimedia]Camera 구현 세부 정보  (0) 2017.09.15
Comments