본문 바로가기
SWE/스트리밍

Qt 동영상 플레이어 예제 코드 분석 | GStreamer

by S나라라2 2022. 8. 4.
반응형

Qt-Gstreamer에서 예제 코드를 몇 개 제공한다. 

그 중에서 player코드를 분석해보자.

 

해당 플레이어는 pc에 저장된 동영상 파일을 읽어와서 재생, 정지, 일시정지 기능을 제공한다.

 

코드 구조

- 매우 심플하다.

- main을 제외하고 두 개의 코드 파일만 필요하다. (mediaapp, player)

 

코드의 큰 흐름으로 먼저 따라가보자

 

 

1. 응용프로그램 실행

// main.cpp

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QGst::init(&argc, &argv);

    MediaApp media;
    media.show();

    if (argc == 2) {
    	// argv[1] : 파일 uri
        media.openFile(argv[1]);
    }

    return app.exec();
}

- MediaApp이라는 클래스를 생성하고 argument로 받은 파일의 경로를 전달해준다. openFile()

    MediaApp media;
    media.show();

    if (argc == 2) {
        media.openFile(argv[1]);
    }

 

 

2. MediaApp 클래스 생성자

//mediaapp.cpp
MediaApp::MediaApp(QWidget *parent)
    : QWidget(parent)
{
    //create the player
    m_player = new Player(this);
    connect(m_player, SIGNAL(positionChanged()), this, SLOT(onPositionChanged()));
    connect(m_player, SIGNAL(stateChanged()), this, SLOT(onStateChanged()));

    //m_baseDir is used to remember the last directory that was used.
    //defaults to the current working directory
    m_baseDir = QLatin1String(".");

    //this timer (re-)hides the controls after a few seconds when we are in fullscreen mode
    m_fullScreenTimer.setSingleShot(true);
    connect(&m_fullScreenTimer, SIGNAL(timeout()), this, SLOT(hideControls()));

    //create the UI
    QVBoxLayout *appLayout = new QVBoxLayout;
    appLayout->setContentsMargins(0, 0, 0, 0);
    createUI(appLayout);
    setLayout(appLayout);

    onStateChanged(); //set the controls to their default state

    setWindowTitle(tr("QtGStreamer example player"));
    resize(400, 400);
}

- MedaApp 클래스 의 생성자이다.

 

- 첫 줄에서 보면 알 수 있듯이 비디오의 스트리밍을 제어하는 player라는 객체가 따로 있다.

- 즉, MediaApp : GUI 윈도우 창, Player: 비디오 스트리밍 컨트롤 이다.

    //create the player
    m_player = new Player(this);
 
    //create the UI
    QVBoxLayout *appLayout = new QVBoxLayout;
    appLayout->setContentsMargins(0, 0, 0, 0);
    createUI(appLayout);
    setLayout(appLayout);

 

 

3. MediaApp::openFile()

main에서 두 번째 argument를 openFile() 메서드로 넘겨줬다. 그 흐름을 따라서 openFile() 메서드를 살펴보자.

void MediaApp::openFile(const QString & fileName)
{
    m_baseDir = QFileInfo(fileName).path();

    m_player->stop();
    m_player->setUri(fileName);
    m_player->play();
}

- 비디오 플레이어 윈도우 창인 MediaApp에서 파일의 이름을 인자로 전달 받았다.

- 해당 인자를 스트리밍 컨트롤을 담당하는 player에게 넘겨준다. setUri()

 

 

4.Player::setUri()

void Player::setUri(const QString & uri)
{
    QString realUri = uri;

    //if uri is not a real uri, assume it is a file path
    if (realUri.indexOf("://") < 0) {
        realUri = QUrl::fromLocalFile(realUri).toEncoded();
    }

    if (!m_pipeline) {
        m_pipeline = QGst::ElementFactory::make("playbin").dynamicCast<QGst::Pipeline>();
        if (m_pipeline) {
            //let the video widget watch the pipeline for new video sinks
            watchPipeline(m_pipeline);

            //watch the bus for messages
            QGst::BusPtr bus = m_pipeline->bus();
            bus->addSignalWatch();
            QGlib::connect(bus, "message", this, &Player::onBusMessage);
        } else {
            qCritical() << "Failed to create the pipeline";
        }
    }

    if (m_pipeline) {
        m_pipeline->setProperty("uri", realUri);
    }
}

- player의 로컬변수 m_pipeline이 null인지 확인한다.

- 만약 null일 경우, pipeline을 새로 생성해준다.

- playbin을 이용해 파이프라인을 만든다. 

     *playbin : playbin을 이용하면 오직 하나의 element만을 사용해서 파이프라인을 구성할 수 있다. playbin은 Gstreamer에서 제공하는 특별한 element인데, source로서도 동작을하고, sink로서도 동작을 한다. 즉 파이프라인의 전체를 담당할 수 있다. playbin을 생성하고, 미디어 URI만 넘겨주면 쉽게 플레이를 할 수 있다. (GStreamer Basic tutorial 1 참고)

     *QGst의 ElementFactory를 이용해서 플레이빈 엘레먼트를 만든다.

if (!m_pipeline) {
        m_pipeline = QGst::ElementFactory::make("playbin").dynamicCast<QGst::Pipeline>();

- GUI 윈도우 창이 새로운 비디오 싱크를 위해 pipeline을 대기하고 있는다.

watchPipeline(m_pipeline);

- 파이프라인의 이벤트 메시지를 전달 받을 수 있도록  bus를 대기하고 있는다.

- 그리고 bus에서 메시지가 오면 여기 클래스에서 처리할 수 있도록 connect해놓는다.

     *기본 Qt에서는 Q_OBJECT를 클래스 헤더에 선언해놓고 connect()를 사용해서 시그널 슬롯 함수를 연결하면 된다. 

      그러나 여기에서는 QGlib의 connect를 사용해야 gstreamer의 이벤트를 받을 수 있다.

QGst::BusPtr bus = m_pipeline->bus();
bus->addSignalWatch();
QGlib::connect(bus, "message", this, &Player::onBusMessage);

-파이프라인에 미디어의 uri를 등록해준다.

if (m_pipeline) {
        m_pipeline->setProperty("uri", realUri);
    }

 

5.Player::play()

MediaApp::openFile() 메서드에서 uri를 등록해준 후, player->play() 메서드를 호출한다. 

따라서 play함수를 살펴보자.

void Player::play()
{
    if (m_pipeline) {
        m_pipeline->setState(QGst::StatePlaying);
    }
}

- 파이프라인의 상태값을 StatePlaying으로 바꿔주기만 하면 된다.

     * pipeline 클래스는 element 클래스를 상속받아 setState()함수의 사용이 가능하다. 

- Player::play() 함수는 두 가지 경우에 호출된다.

     (1) 동영상 path를 지정해줄 때. MediaApp::openFile() -> Player::setUri() -> Player::play()

     (2) 동영상 플레이어 애플리케이션의 '재생'버튼을 누를 때. playButton 시그널 clicked() -> Player::play()

 

 

반응형