이전 튜토리얼까지 진행하면서 gstreamer에 대한 기초를 살펴봤습니다. 이를 활용해서 간단한 미디어 플레이어를 제작해보는 예제를 진행하려고 합니다. Tutorial 5는 GTK를 기반으로 예제를 진행하기 때문에 많은 참고는 할 수 없지만 여기서 중요한 함수를 확인할 수 있습니다.
이전까지 예제를 실행하면 Direct 3D 11 renders라는 창이 뜨면서 영상이 재생되었습니다. 이 창을 저희 UI에 이식시켜야 하는게 가장 문제였는데 아래 함수를 통해 이 문제를 해결할 수 있습니다.
gst_video_overlay_set_window_handle을 통해 위젯의 handle 값을 넘겨 해당 위젯에서 Play(Draw)가 가능합니다.
그리고 해당 함수를 사용하기 위해서는 lgstvideo-1.0 라이브러리를 사용해야 하기 때문에 project 파일도 아래처럼 추가하였습니다.
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
MsgBusCheckThread.cpp \
StreamMgr.cpp \
main.cpp \
GstreamerPlayer.cpp
HEADERS += \
Common.h \
GstreamerPlayer.h \
MsgBusCheckThread.h \
StreamMgr.h
FORMS += \
GstreamerPlayer.ui
INCLUDEPATH += \
$$PWD/include \
$$PWD/include/gstreamer-1.0 \
$$PWD/include/glib-2.0/ \
$$PWD/include/glib-2.0/include \
$$PWD/lib/glib-2.0/include \
$$PWD/include/orc-0.4 \
win32: LIBS += -L$$PWD/lib/ -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0 -lintl -lgstvideo-1.0 -lgstbase-1.0
DESTDIR += \
$$PWD/bin
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
실행화면은 아래와 같습니다. 실제 play 영상은 QFrame 안에 재생이 되고 있고 Play, Pause, Stop 버튼과 재생 길이를 확인할 수 있는 Slider와 그 밑에는 현재 영상 시점, 영상 길이를 알 수 있는 Label로 이루어져 있습니다.
GstreamerPlayer MainWindow 코드입니다. StreamMgr를 통해 Gstreamer를 control 합니다. play 시에 QFrame의 Handle 값을 넘겨주게 됩니다. StreamMgr를 통해 받아온 영상 시간 관련 정보를 label과 slider에 표시해줍니다.
#include "GstreamerPlayer.h"
#include "ui_GstreamerPlayer.h"
#include <QTime>
GstreamerPlayer::GstreamerPlayer(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::GstreamerPlayer)
, pMgr(NULL)
{
ui->setupUi(this);
pMgr = new StreamMgr(this);
connect(ui->btnPlay, SIGNAL(clicked(bool)), this, SLOT(Slot_Play()));
connect(ui->btnPause, SIGNAL(clicked(bool)), this, SLOT(Slot_Pause()));
connect(ui->btnStop, SIGNAL(clicked(bool)), this, SLOT(Slot_Stop()));
connect(pMgr, SIGNAL(emit_changeDuration(int)), this, SLOT(Slot_StreamDuration(int)));
connect(pMgr, SIGNAL(emit_currentTime(int)), this, SLOT(Slot_CurrentTime(int)));
}
GstreamerPlayer::~GstreamerPlayer()
{
delete ui;
delete pMgr;
}
void GstreamerPlayer::Slot_Play()
{
if ( pMgr == NULL ) { return; }
pMgr->Play();
pMgr->SetWindowHandle(ui->frame->winId());
// QFrame의 Handle 값 전달
}
void GstreamerPlayer::Slot_Pause()
{
if ( pMgr == NULL ) { return; }
pMgr->Pause();
}
void GstreamerPlayer::Slot_Stop()
{
if ( pMgr == NULL ) { return; }
pMgr->Stop();
}
void GstreamerPlayer::Slot_StreamDuration(int nDuration)
{
ui->bar->setRange(0, nDuration);
QTime time(0,0,0);
time = time.addSecs(nDuration);
ui->lbDuration->setText(time.toString());
}
void GstreamerPlayer::Slot_CurrentTime(int nTime)
{
ui->bar->setValue(nTime);
QTime time(0,0,0);
time = time.addSecs(nTime);
ui->lbCurrent->setText(time.toString());
}
전체적인 Gstreamer 관리는 StremMgr이란 클래스에서 이뤄집니다. 이 클래스에서 체크해야할 점은 msg를 처리하기 위해 Thread를 따로 생성했다는 것과 GstreamerPlayer에서 전달받은 Handle 값을 gst_video_overlay_set_window_handle에 넘겨줍니다.
#include <QDebug>
#include "StreamMgr.h"
#include "gst/video/videooverlay.h"
StreamMgr::StreamMgr(QObject *parent)
: QObject{parent}
, pThread(NULL)
{
Init();
pThread = new MsgBusCheckThread();
if ( pThread == NULL || bus == NULL || data == NULL ) { return; }
connect(pThread, SIGNAL(emit_changeDuration(int)), this, SLOT(slot_getDuration(int)));
connect(pThread, SIGNAL(emit_currentTime(int)), this, SLOT(slot_getTime(int)));
pThread->SetBus(bus);
pThread->setData(data);
pThread->start();
}
StreamMgr::~StreamMgr()
{
if ( pThread->isRunning() ) {
pThread->quit();
pThread->wait();
}
delete data;
}
void StreamMgr::Init()
{
/* Initialize GStreamer */
gst_init (NULL, NULL);
data = new CustomData;
if ( data == NULL ) { return; }
/* Create the elements */
data->playbin = gst_element_factory_make ("playbin", "playbin");
if (!data->playbin) {
g_printerr ("Not all elements could be created.\n");
return;
}
/* Set the URI to play */
g_object_set (data->playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
/* Listen to the bus */
bus = gst_element_get_bus (data->playbin);
}
void StreamMgr::slot_getDuration(int nDuration)
{
emit emit_changeDuration(nDuration);
}
void StreamMgr::slot_getTime(int nTime)
{
emit emit_currentTime(nTime);
}
void StreamMgr::SetPlayBinState(GstState State)
{
ret = gst_element_set_state (data->playbin, State);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (data->playbin);
return;
}
}
void StreamMgr::Pause()
{
SetPlayBinState(GST_STATE_PAUSED);
}
void StreamMgr::Play()
{
SetPlayBinState(GST_STATE_PLAYING);
}
void StreamMgr::Stop()
{
SetPlayBinState(GST_STATE_READY);
}
void StreamMgr::SetWindowHandle(qint64 nID)
{
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->playbin), static_cast<guintptr>(nID));
}
마지막으로 msg를 처리할 Thread입니다. 나머지 코드는 이전 튜토리얼과 크게 다를것이 없고 gstTimeToSecond를 통해 GST_TIME_FORMAT 형태의 시간 표시를 QTime을 통해 Seconds 값으로 변경해줍니다.
#include "MsgBusCheckThread.h"
#include <QDebug>
#include <QTime>
MsgBusCheckThread::MsgBusCheckThread()
: QThread()
, bus(NULL)
, msg(NULL)
, data(NULL)
{
}
void MsgBusCheckThread::run()
{
if ( bus == NULL ) { return; }
while (true) {
msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND, (GstMessageType)(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION));
/* Parse message */
if (msg != NULL) {
HandleMessage (data, msg);
} else {
/* We got no message, this means the timeout expired */
gint64 current = -1;
/* Query the current position of the stream */
if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, ¤t)) {
int nCurrent = gstTimeToSecond(current);
emit emit_currentTime(nCurrent);
}
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) {
int nDuration = gstTimeToSecond(data->duration);
emit emit_changeDuration(nDuration);
}
}
}
}
}
void MsgBusCheckThread::HandleMessage(CustomData *data, GstMessage *msg)
{
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error (&err);
g_free (debug_info);
data->terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print ("\nEnd-Of-Stream reached.\n");
data->terminate = TRUE;
break;
case GST_MESSAGE_DURATION:
/* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE;
break;
case GST_MESSAGE_STATE_CHANGED: {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
/* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING);
if (data->playing) {
/* We just moved to PLAYING. Check if seeking is possible */
GstQuery *query;
gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->playbin, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
} else {
g_print ("Seeking is DISABLED for this stream.\n");
}
}
else {
g_printerr ("Seeking query failed.");
}
gst_query_unref (query);
}
}
} break;
default:
/* We should not reach here */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
int MsgBusCheckThread::gstTimeToSecond(gint64 nTime)
{
QString sTime = QString::asprintf("%" GST_TIME_FORMAT, GST_TIME_ARGS (nTime));
sTime = sTime.remove(sTime.indexOf("."),sTime.length()-sTime.indexOf("."));
QTime tTime = QTime::fromString(sTime,"h:mm:ss");
int nSecond = QTime(0,0,0).secsTo(tTime);
return nSecond;
}
void MsgBusCheckThread::SetBus(GstBus *pBus)
{
bus = pBus;
}
void MsgBusCheckThread::setData(CustomData *pData)
{
data = pData;
}
자세한 코드는 아래 링크 참고 부탁드립니다. Seek 및 기타 기능은 다음 기회에 추가해보도록 하겠습니다.
'Programming > Gstreamer' 카테고리의 다른 글
[Gstreamer/Qt] Gstreamer(7) - buffer, GLib (0) | 2023.03.24 |
---|---|
[Gstreamer/Qt] Gstreamer(6) - Pad template (0) | 2023.03.23 |
[Gstreamer/Qt] Gstreamer(4) - query (0) | 2023.03.21 |
[Gstreamer/Qt] Gstreamer(3) - Pad, GSignal (0) | 2023.03.20 |
[Gstreamer/Qt] Gstreamer(2) - Element, pipeline (0) | 2023.03.20 |