Qt 로 만들자: libvlc 를 이용한 간단한 동영상 재생기 LVPlayer

0. 들어가면서...

지난 번에 MPlayer 의 slave 모드를 이용해서 MPlayer 의 front-end 를 만드는 방법을 살펴보았다. 이번에는 오픈 소스 동영상 재생기 중에서 MPlayer 만큼이나 유명한 VLC Media Player 에 대해 알아보고자 한다.

VLC Media Player 는 MPlayer 와 달리 라이브러리 형태로 모든 기능을 제공한다. 이를 libvlc 또는 VLC SDK 라고 한다. 이론적으로 libvlc 를 이용하면 VLC Media Player 와 동등한 기능을 가진 동영상 재생기를 만들 수 있다.

이 프로젝트에서는 지난 MPGui 프로젝트에서처럼 아주 간단한 기능만 구현해보도록 하자. 그리고 MPGui 처럼 Windows 7 을 기본으로 하며, 사용된 VLC Media Player 버전은 2.2.4 이다. 그럼에도 불구하고, 다른 OS 나 플랫폼에서도 약간의 코드만 수정하면 충분히 작동할 것이다.

0.1. 사전 준비

libvlc 는 VLC Media Player 의 설치 프로그램에는 포함되어 있지 않다. 그렇다면 VLC Media Player 의 소스를 받아서 빌드해야 하느냐? 물론 그래도 되지만, 꼭 그럴 필요는 없다. 7-zip 배포본에는 sdk 디렉토리에 libvlc 가 포함되어 있다. 따라서 설치 프로그램이 아니라 7-zip 배포본을 받으면 된다.

0.1.1. libvlc( VLC SDK ) 설치하기

7-zip 배포본은 다음 사이트에서 받을 수 있다.


이 때 [Download VLC] 를 누르지 말고 옆에 있는 아래쪽 화살표(🔻)를 누르면 여러가지 형태의 배포본이 나온다. 그 중에서 [7-zip package] 를 선택하면 된다.

7-zip package 를 받았으면, 원하는 디렉토리에 풀면 된다.

0.1.2. PATH 환경 변수 바꾸기

VLC Media Player 는 SDK 를 DLL 형태로만 제공한다. 따라서 실행할 때에는 해당 DLL 들을 로드할 수 있어야 한다. 이를 위해 [PATH] 환경 변수를 바꿔야 한다. 바꾸는 방법은 다음과 같다.

  1. [시작] 메뉴 클릭
  2. [컴퓨터] 항목에서 오른쪽 버튼 클릭
  3. 팝업 메뉴에서 [속성] 클릭 
  4. 왼쪽 패널에서 [고급 시스템 설정 클릭]
  5. [시스템 속성] 대화 상자에서 [환경 변수(N)...] 클릭
  6. [변수] 에서 [PATH] 환경 변수 선택
  7. [편집] 클릭 또는 [PATH] 환경 변수 더블 클릭
  8. [변수 값] 가장 마지막에 7-zip package 를 풀어 놓은 디렉토리(libvlc.dll 과 libvlccore.dll 이 있는 디렉토리) 추가(예: ....;vlc디렉토리)

이렇게 [PATH] 환경 변수에 VLC Media Player 의 경로를 추가하면 된다.

참고로, 내 경우에는 설치 프로그램으로 VLC Media Player v2.2.4 를 설치하였고, sdk 디렉토리만 7-zip package 에서 추출하였다.

1. 요구사항

  • 동영상을 Qt 창에서 재생한다
  • 탐색을 지원한다(10초 전후방 탐색)
  • 재생/정지를 지원한다
  • 일시정지를 지원한다 

2. 코드 분석

2.1 프로젝트 작성

  • 프로젝트 이름: lvplayer
  • 메인 클래스 이름: LVPlayer
  • 메인 클래스 유형: QMainWindow

2.1.1. 프로젝트 파일 수정(lvplayer.pro)

외부 라이브러리를 쓰기 위해 헤더 파일 경로와 라이브러리 경로를 설정해야한다. 컴파일러 환경 변수를 이용해도 되지만, 프로젝트 파일에서 추가하는 것이 더 확장성 있다. 추가된 내용은 다음과 같다.

SDKPATH = $$PWD/sdk
INCLUDEPATH += $$SDKPATH/include
LIBS += -L$$SDKPATH/lib -lvlc


SDKPATH 는 libvlc 가 설치되어 있는 디렉토리 경로이다. 위 경우는 7-zip package 의 sdk 디렉토리를 LVPlayer 프로젝트 하위 디렉토리에 복사한 것이다. 다른 경우라면 SDKPATH 를 환경에 맞게 수정해야 한다.

$$ 연산자는 qmake 에서 변수 값을 읽는데 쓰인다. PWD 는 프로젝트 파일이 있는 디렉토리 경로를 담고 있는 qmake 변수이다. INCLUDEPATH 는 헤더 파일의 경로를 나타내는 qmake 변수이고, += 연산자는 주어진 내용을 추가한다. LIBS 는 라이브러리를 찾을 디렉토리와 링크에 쓰이는 라이브러리를 나타내는 qmake 변수이다. libvlc 에 링크하기 위해서는 -lvlc 가 필요하다. Windows 환경에서도 qmake 가 알아서 -L 과 -l 옵션을 링커에 맞게 바꾸어준다.

2.2. 헤더 분석(lvplayer.h)

2.2.1. 헤더 파일 목록

1
2
3
4
5
#include <QMainWindow>

#include <vlc/vlc.h>

#include <QtWidgets>


2 번째 줄: libvlc 헤더는 vlc/vlc.h 이다.

2.2.2. public 멤버 함수

1
2
3
public:
    LVPlayer(QWidget *parent = 0);
    ~LVPlayer();


2.2.3. protected 멤버 함수

1
2
protected:
    bool eventFilter(QObject *o, QEvent *e);


2.2.4. private 멤버 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
private:
    /// libvlc 인스턴스
    libvlc_instance_t *_vlc;
    /// libvlc 미디어 플레이어
    libvlc_media_player_t *_vlc_player;
    /// libvlc 이벤트 관리자
    libvlc_event_manager_t *_vlc_player_event;

    QWidget *_movieWidget;  ///< 동영상 위젯
    QPushButton *_playPush; ///< 재생 버튼
    QPushButton *_stopPush; ///< 정지 버튼

    QString _movieFilePath; ///< 동영상 파일 경로


2~7 번째 줄: libvlc_instance_t 는 libvlc 인스턴스를 나타내고, libvlc 를 쓰기 위해 가장 먼저 생성해야 한다. 그리고 여러 개의 인스턴스를 만드는 것도 가능하다. libvlc_media_player_t 는 미디어 플레이어를 나타내고, 이름에서 알 수 있듯이 재생 관련 기능을 담당한다. libvlc_event_manager_t 는 libvlc 에서 발생하는 이벤트들를 관리한다. libvlc 에서 이벤트 알림을 받고 싶을 때 쓰인다. 그리고 나중에 알아보겠지만, 미디어에 관련된 타입은 libvlc_media_t 이다.

2.2.5. private 멤버 함수

1
2
3
4
    void initMenus();
    void initWidgets();

    void setPlayText(bool play = true);


2.2.6. private slots

1
2
3
4
private slots:
    void fileOpen();
    void play();
    void stop();


2.3. 소스 코드 분석(lvplayer.cpp)

2.3.1. LVPlayer 클래스

2.3.1.1. 생성자와 소멸자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * @brief LVPlayer 생성자
 * @param parent 부모 위젯
 */

LVPlayer::LVPlayer(QWidget *parent)
    : QMainWindow(parent)
{
    // libvlc 인스턴스 생성
    _vlc = libvlc_new(0, 0);
    // libvlc 미디어 플레이어 생성
    _vlc_player = libvlc_media_player_new(_vlc);
    // libvlc 이벤트 관리자 생성
    _vlc_player_event = libvlc_media_player_event_manager(_vlc_player);
    // libvlc 이벤트 콜백 등록. 끝까지 재생했을 때 호출
    libvlc_event_attach(_vlc_player_event, libvlc_MediaPlayerEndReached,
                        vlc_player_cb, this);

    QApplication::setApplicationDisplayName(tr("LVPlayer"));

    initMenus();
    initWidgets();
}

/**
 * @brief LVPlayer 소멸자
 */

LVPlayer::~LVPlayer()
{
    // 정지
    stop();

    // 이벤트 콜백 제거
    libvlc_event_detach(_vlc_player_event, libvlc_MediaPlayerEndReached,
                        vlc_player_cb, this);

    // libvlc 미디어 플레이어 해제
    libvlc_media_player_release(_vlc_player);
    // libvlc 인스턴스 해제
    libvlc_release(_vlc);
}


8~16 번째 줄: libvlc_new() 는 새로운 libvlc 인스턴스를 생성한다. libvlc_new 는 main() 처럼 argc 와 argv 를 받는다. 특별히 넘겨줄 인수가 없으니 0, 0 을 넘겼다. 만약 libvlc 의 내부의 다양한 메세지를 보고 싶다면 다음처럼 하면 된다.

1
2
3
const char *vlc_args[] = {"-vvv"};

_vlc = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args);


이뿐만 아니라, VLC Media Player 를 실행할 때 쓰이는 다양한 옵션들을 쓸 수 있다.

libvlc_media_player_new() 는 libvlc 인스턴스로부터 새로운 미디어 플레이어를 생성한다. libvlc_media_player_event_manger() 는 미디어 플레이어로부터 이벤트 관리자를 얻는다. libvlc_event_attach() 는 이렇게 얻은 이벤트 관리자의 특정한 이벤트에 콜백을 등록한다. 여기에서는 libvlc_MediaPlayerEndReached, 곧 끝까지 재생했을 때 발생하는 이벤트이다.

29~39 번째 줄: 리소스들을 정리한다. 재생을 중단하고, libvlc_event_detach() 로 콜백을 제거하고, libvlc_media_player_release() 로 미디어 플레이어를 해제하며, libvlc_release() 로 libvlc 인스턴스를 정리한다.

이쯤에서 눈썰미 좋은 사람들은 눈치챘을지도 모르지만, libvlc 의 일반적인 메모리 관리 패턴은 libvlc_object_new() 로 생성하고, libvlc_object_release() 로 해제한다.

libvlc_object_new()
...
libvlc_object_release()

이 프로젝트에서는 libvlc_new()/libvlc_release(), libvlc_media_player_new()/libvlc_media_player_release(), libvlc_media_new_path()/libvlc_media_release() 이다.

2.3.1.2. initMenus()

메뉴를 초기화한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * @brief 메뉴를 초기화한다
 */

void LVPlayer::initMenus()
{
    QMenu *fileMenu = new QMenu(tr("파일(&F)"));
    fileMenu->addAction(tr("열기(&O)"), this, SLOT(fileOpen()),
                        QKeySequence::Open);
    fileMenu->addSeparator();
    fileMenu->addAction(tr("끝내기(&x)"), this, SLOT(close()),
                        QKeySequence(tr("Ctrl+Q")));

    menuBar()->addMenu(fileMenu);
}


2.3.1.3. initWidgets()

위젯을 초기화한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * @brief 위젯을 초기화한다
 */

void LVPlayer::initWidgets()
{
    // 재생 버튼
    _playPush = new QPushButton;
    setPlayText();

    // 정지 버튼
    _stopPush = new QPushButton(tr("정지(&S)"));

    // 가로로 배치
    QHBoxLayout *hbox = new QHBoxLayout;
    hbox->addWidget(_playPush);
    hbox->addWidget(_stopPush);
    hbox->addStretch(1);


재생/정지 버튼을 만들고 가로로 배치한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    // 동영상 위젯
    _movieWidget = new QWidget;
    _movieWidget->setAttribute(Qt::WA_OpaquePaintEvent);
    _movieWidget->installEventFilter(this);

    // 세로로 배치
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->setContentsMargins(0, 0, 0, 0);
    vbox->addWidget(_movieWidget);
    vbox->addLayout(hbox);

    // 시그널/슬롯 연결
    connect(_playPush, SIGNAL(clicked(bool)), this, SLOT(play()));
    connect(_stopPush, SIGNAL(clicked(bool)), this, SLOT(stop()));

    // 위젯 생성
    QWidget *w = new QWidget;
    w->setLayout(vbox);

    setCentralWidget(w);

    // 초기 크기
    resize(640, 480);

    // 입력 촛점 설정
    _movieWidget->setFocus();
}


동영상을 표시할 위젯을 생성하고 아랫쪽에 버튼을 배치한다.

8 번째 줄: QLayout::setContentsMargins() 는 레이아웃 바깔쪽 경계의 폭을 정한다. 인수는 왼쪽부터 시계 방향 순이다. 곧, 왼쪽, 윗쪽, 오른쪽, 아랫쪽 순이다.

2.3.1.4. fileOpen()

동영상 파일을 연다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @brief 동영상 파일을 연다
 */

void LVPlayer::fileOpen()
{
    // 필터 목록
    static QString filter(tr("비디오 (*.mkv *.mp4 *.avi *.mpg);;"
                             "모든 파일 (*)"));
    // 파일 이름을 얻음
    QString filePath(QFileDialog::getOpenFileName(this, QString(), QString(),
                                                  filter));

    // 파일을 선택했으면 재생
    if (!filePath.isEmpty())
    {
        _movieFilePath = filePath;
        play();
    }
}


MPGui 때와 큰 차이가 없다.

2.3.1.5. setPlayText()

재생 버튼의 제목을 설정한다.

1
2
3
4
5
6
7
8
9
10
11
/**
 * @brief 재생 버튼의 제목을 설정한다
 * @param play 재생 버튼이면 true, 일시정지 버튼이면 false
 */

void LVPlayer::setPlayText(bool play)
{
    if (play)
        _playPush->setText(tr("재생(&P)"));
    else
        _playPush->setText(tr("일시정지(&P)"));
}


_playPush->setText() 를 여러 군데에서 직접 호출해서 재생 버튼의 제목을 바꿀 수도 있지만, 그렇게 하면 번역의 양이 늘어난다. 따라서 가능하면 이렇게 모음으로써 번역의 양을 줄일 수 있고, 아울러 번역 실수도 줄일 수 있다.

2.3.1.6. play()

동영상 파일을 재생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * @brief 동영상을 재생한다
 */

void LVPlayer::play()
{
    // 입력 촛점은 언제나 동영상 위젯에
    _movieWidget->setFocus();

    // 재생 상태
    int playing = libvlc_media_player_is_playing(_vlc_player);
    // 일시정지 상태
    int paused = libvlc_media_player_get_state(_vlc_player) == libvlc_Paused;

    // 재생 버튼을 누른 경우에만 일시정지/재생 실행
    if (sender() == _playPush && (playing || paused))
    {
        libvlc_media_player_set_pause(_vlc_player, playing);
        setPlayText(playing);

        return;
    }

    // 동영상 파일을 아직 고르지 않았으면 파일을 열기부터
    if (_movieFilePath.isEmpty())
    {
        fileOpen();

        return;
    }


7 번째 줄: 버튼을 클릭하면 입력 촛점이 버튼으로 옮겨진다. 이렇게 되면, 다음에 키보드를누르더라도 키보드 입력이 동영상 위젯에 전달되지 않는다. 따라서 입력 촛점을 동영상 위젯으로 옮겨야한다.

9~12 번째 줄: libvlc_media_player_is_playing() 는 현재 미디어가 재생 중인지 알려주고, libvlc_media_player_get_state() 는 재생중(libvlc_Playing), 일시정지(libvlc_Paused) 따위의 현재 플레이어 상태를 알려준다. libvlc_media_plyaer_is_playing()libvlc_media_player_get_state() 의 값이 libvlc_Playing 또는 libvlc_Buffering 일 때와 같다.

15 번째 줄: QObject::sender() 는 슬롯을 호출한 시그널 객체를 뜻한다. 그런데 QObject::sender() 는 QObject::connect() 로 연결되지 않더라도 시그널 처리 중에 호출되더라도 값을 가질 수 있다. 예를 들어, 메뉴에서 열기를 했을 때에도 play() 가 호출되는데, 이 때에도 QObject::sender() 는 값이 가지며, 그 값은 QAction 객체를 가리킨다. 따라서 구체적으로 호출자를 확인하는 것이 좋다.

17 번째 줄: libvlc_media_player_set_pause() 는 일시정지/다시재생을 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    // 로컬 파일에 대한 미디어를 생성
    libvlc_media_t *vlc_media = libvlc_media_new_path(_vlc,
                                                      qtv(_movieFilePath));
    // 미디어를 libvlc 미디어 플레이어에 등록
    libvlc_media_player_set_media(_vlc_player, vlc_media);
    // 등록한 미디어는 해제
    libvlc_media_release(vlc_media);

    // 동영상을 표시할 위젯 설정
    libvlc_media_player_set_hwnd(_vlc_player,
                                 reinterpret_cast<void*>(
                                     _movieWidget->winId()));

    // 동영상 재생
    libvlc_media_player_play(_vlc_player);

    // 재생 버튼 일시정지 상태로
    setPlayText(false);
}


1~15 번째 줄: libvlc_media_t 는 미디어에 대한 타입이고, libvlc_media_new_path() 는 로컬 파일 시스템에 있는 미디어를 연다. qtv() 는 Qt 스타일 경로를 libvlc 스타일 경로로 바꾸어주는 유틸리티 함수이다. 나중에 다시 설명한다. 네트워크 스트림을 열고 싶다면, libvlc_media_new_location() 을 쓰면 된다. libvlc_media_player_set_media() 는 미디어 플레이어가 재생할 미디어를 설정한다. 일단 미디어 플레이어에 전달된 미디어는 libvlc_media_release() 로 해제해도 된다.

libvlc_media_player_set_hwnd() 는 동영상을 표시할 위젯을 설정한다. 다른 OS/플랫폼에서는 다른 함수를 써야 한다. 일반적인 형태는 다음과 같다.

1
2
3
4
5
6
7
#if defined(Q_OS_MAC)
    libvlc_media_player_set_nsobject(_vlc, (void *)_movieWidget->winId());
#elif defined(Q_OS_UNIX)
    libvlc_media_player_set_xwindow(_vlc, _movieWidget->winId());
#elif defined(Q_OS_WIN)
    libvlc_media_player_set_hwnd(_vlc, (void *)_movieWidget->winId());
#endif


2.3.1.7. stop()

재생을 중단한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * @brief 동영상 재생을 정지한다
 */

void LVPlayer::stop()
{
    // 입력 촛점은 언제나 동영상 위젯에
    _movieWidget->setFocus();

    // 재생을 정지
    libvlc_media_player_stop(_vlc_player);

    // 재생 버튼 재생 상태로
    setPlayText(true);
}


10 번째 줄: libvlc_media_player_stop() 는 미디어 플레이어의 재생을 중단한다.

2.3.1.8. eventFilter()

동영상 위젯의 키보드 입력을 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
bool LVPlayer::eventFilter(QObject *o, QEvent *e)
{
    if (o == _movieWidget)
    {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);

        // 재생 상태
        int playing = libvlc_media_player_is_playing(_vlc_player);
        // 일시 정지 상태
        int paused = libvlc_media_player_get_state(_vlc_player)
                        == libvlc_Paused;

        // 현재 재생 시간
        libvlc_time_t current = libvlc_media_player_get_time(_vlc_player);

        switch (ke->key())
        {
        case Qt::Key_Left:
            // 10 초 이전으로
            libvlc_media_player_set_time(_vlc_player, current - 10 * 1000);
            return true;

        case Qt::Key_Right:
            // 10 초 이후로
            libvlc_media_player_set_time(_vlc_player, current + 10 * 1000);
            return true;

         case Qt::Key_Space:
            // 일시정지/재생
            if (playing || paused)
                _playPush->click();
            return true;
        }
    }

    // 나머지는 부모 클래스에게
    return QMainWindow::eventFilter(o, e);
}


14 번째 줄: libvlc_media_player_get_time() 은 현재 재생 시간을 밀리초 단위로 알려준다.

20, 25 번째 줄: libvlc_media_player_set_time() 은 새로운 재생 위치를 밀리초 단위로 정한다.

2.3.2. 정적 함수

2.3.2.1. vlc_player_cb()

미디어 끝까지 재생했으면 재생을 중단한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * @brief libvlc 미디어 플레이어 이벤트 콜백
 * @param e libvlc 이벤트 종류
 * @param p 사용자 데이터
 */

static void vlc_player_cb(const struct libvlc_event_t *e, void *p)
{
    // 끝까지 재생했으면 정지
    if (e->type == libvlc_MediaPlayerEndReached)
    {
        LVPlayer *lvplayer = reinterpret_cast<LVPlayer *>(p);

        QMetaObject::invokeMethod(lvplayer, "stop", Qt::QueuedConnection);
    }
}


6 번째 줄: struct libvlc_event_t 는 이벤트 종류이다. 콜백을 등록할 때 전달한 사용자 데이터가 두 번째 인수로 전달된다. libvlc_event_t.type 은 이벤트 종류를 나타낸다.

13 번째 줄: 재생을 중단하기 위해 LVPlayer::stop() 을 호출하는 코드이다. 콜백내에서 직접 호출하게 되면, DEAD-LOCK 상태에 빠지기 때문에, 이를 피하기 위해 Qt::QueuedConnection 을 이용하여 호출한다. 이렇게 하면 이벤트 큐에 추가하고나서 나중에 실행된다.

그런데 LVPlayer::stop() 은 private slots 이다. 그럼에도 불구하고, 외부 함수에서 호출이 가능한데, 이는 Qt 의 버그인 듯하다. 일단 문제가 발생하지 않기에 그냥 두기는 했지만, 원칙적으로는 LVPlayer::stop() 을 public slots 로 바꾸어야 한다.

2.3.2.2. qtv()

Qt 스타일 경로를 libvlc 스타일 경로로 바꾼다.

1
2
3
4
5
6
7
8
9
/**
 * @brief Qt 스타일 경로를 libvlc 스타일 경로로 바꾼다
 * @param s Qt 경로
 * @return libvlc 스타일 경로
 */

static inline const char *qtv(const QString &s)
{
    return QDir::toNativeSeparators(s).toUtf8().constData();
}


8 번째 줄: QDir::toNativeSeparators() 는 디렉토리 구분자를 OS 또는 플랫폼에 맞게 바꾸는 정적 멤버 함수이다. Windows 나 OS/2 의 경우 '\', 나머지는 '/' 이다.

libvlc 는 UTF-8 으로 인코딩된 파일 이름을 받는다. 특히 Windows 에서는 디렉토리 구분자로 '\' 만 받는다. 반면에 Qt 는 언제난 '/' 를 디렉토리 구분자로 쓴다. 그리고 내부적으로 UTF-16 을 쓰기 때문에 libvlc 에 파일 이름을 전달할 때는 위와 같은 변환이 필요한다.

QString::toUtf8() 은 QString 문자열을 UTF-8 QByteArray 문자열로 바꾼다. QByteArray::constData() 는 QByteArray 를 const char * 로 돌려준다.

3. 마무리하면서...

libvlc 를 이용해서 간단한 동영상 재생기를 만들어보았다. libvlc 는 이보다 훨씬 다양하고 강력한 기능들을 제공한다. 이러한 기능들에 대해서는 SDK 헤더를 살펴보기 바란다. 꼭 보기 바란다. 웬만한 기능들은 모두 구현되어 있으니 큰 어려움 없이 자신만의 동영상 재생기를 만들 수 있을 것이다.

다음은 Qt 에서 libvlc 를 이용해서 동영상 재생기를 만드는 방법을 설명하고 있는 사이트들이다. 꼭 읽어보자.

* 참고:

다음은 <LVPlayer> 의 실행 모습이다.


전체 소스는 여기에서 확인하자.


댓글

  1. 개발자님의 코드를 제 컴에서 실행하려고 합니다.
    깃에 올라와있는 lvplayer.pro 코드 중,


    lvplayer.pro 코드에서 20번째 line부터

    SDKPATH = $$PWD/sdk
    INCLUDEPATH += $$SDKPATH/include
    LIBS += -L$$SDKPATH/lib -lvlc

    해당 부분이 vlc lib 7-zip package 를 압축해제한 후, 해당 절대경로를 입력해주었습니다.





    SDKPATH = C:\Users\stuck\Desktop\vlc-3.0.16\sdk
    INCLUDEPATH += C:\Users\stuck\Desktop\vlc-3.0.16\sdk\include
    LIBS += -LC:\Users\stuck\Desktop\vlc-3.0.16\sdk\lib


    이렇게 해주었는데, 에러코드가 71개가 뜹니다..

    에러나는 부분은 lvplayer.cpp, lvplayer.h에서 나고있는데, 사진 첨부가 안되어 모든 에러를 설명드릴수는 없지만


    'libvlc_instance_t' does not name a type
    'libvlc_media_player_t' does no name a type

    'QpushButton' does not name a type 등등이 있고

    use of undeclared identifier 'lvPlayer' 등이 있는데
    뭔가 제가 정의도 안해주고 설정도 안해준 것 같습니다.

    제 컴에서 개발자님 코드 실행을 시키고 싶은데 뭐가 부족한건가요? 도움 부탁드립니다...

    답글삭제
    답글
    1. SDKPATH 값만 바꾸면 INCLUDEPATH 나 LIBS 는 자동으로 바뀌도록 되어 있으니 SDKPATH 값만 설정하시면 됩니다. 그리고 디렉토리 구분자를 역슬래시(\)가 아니라 슬래시(/)를 사용하셔야 합니다. 그래도 안 되시면 다시 댓글 달아주세요.

      삭제
    2. C:\Users\stuck\Desktop\lvplayer\lvplayer.h:32: error: 'QPushButton' does not name a type
      ..\lvplayer\lvplayer.h:32:5: error: 'QPushButton' does not name a type
      QPushButton *_playPush; ///< ъ깮 踰꾪듉
      ^~~~~~~~~~~


      C:\Users\stuck\Desktop\lvplayer\lvplayer.h:25: error: 'libvlc_instance_t' does not name a type
      In file included from ..\lvplayer\main.cpp:1:
      ..\lvplayer\lvplayer.h:25:5: error: 'libvlc_instance_t' does not name a type
      libvlc_instance_t *_vlc;
      ^~~~~~~~~~~~~~~~~


      vlc 라이브러리를 못먹어서 에러가 나는것같습니다...
      그리고 qpushbutton은 왜 에러가 나는걸까요?


      lvplayer.cpp
      lvplayer.h
      lvplayer.pro
      main.cpp

      만 있으면 안되는건가요?



      SDKPATH = $$PWD/C:/Users/stuck/Desktop/vlc-3.0.16/sdk
      INCLUDEPATH += $$SDKPATH/include
      LIBS += -L$$SDKPATH/lib
      이렇게 바꿔보았습니다.

      삭제
    3. 1. 다른 것은 건들지 마시고, SDKPATH 만 이렇게 설정하세요.

      SDKPATH = C:/Users/stuck/Desktop/vlc-3.0.16/sdk

      2. QPushButton 에러는 Qt 헤더가 적절히 포함되지 않아서 생기는 문제로 보입니다. 다른 프로젝트는 문제가 없다면 위 설정 때문에 발생하는 문제일 수도 있으니 일단 위 설정을 바꿔 보시고 다시 시도해 보세요.

      3. 필요한 파일은 말씀하신 4개 파일만 있으면 됩니다. 프로젝트 별로 디렉토리를 구성하였으니 디렉토리에 있는 파일을 모두 받으면 됩니다.

      성공을 기원합니다~^^

      삭제
    4. 1. mingw로 하셨나요? 아니면 msvc 2015 나 2017 32bit 등 어떤것으로 하셨는지 궁금합니다.

      2. ui 파일 없이 어떻게 가능한건지 궁금합니다.

      아직 성공을 못했습니다..... 도와주시면 감사하겠습니다...

      삭제
    5. 아 그리고
      LNK1107: 파일이 잘못되었거나 손상되었습니다. 0x12에서 읽을 수 없습니다.
      C:
      C:\work\QT_project\lvplayer\vlc\sdk\lib\vlc.lib

      라는 에러와

      창에
      vlc.lib

      INPUT(libvlc.lib)

      이란 코드가 떴습니다.

      혹시, vlclib를 (sdk 폴더 있는) 7zip으로 받고 프로젝트 폴더에 그대로 넣으면 되는거 아닌가요? vlc 라이브러리에는 따로 해줄건 없는거 아닌가요? 왜 vlc.lib를 못읽는건가요.....

      삭제
    6. 아! 저 위에 2개는 해결이 되었습니다.


      이제 실행이 됩니다! 그런데 파일에서 동영상 가져와서 play가 안됩니다..

      main input error:입력을 열 수 없습니다
      main input error: VLC에서 'file:///C:/내path/build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug/%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD' MRL을 열 수 없습니다. 자세한 내용은 로그를 확인하세요..

      이건 왜그런걸까요.....

      삭제
    7. 오~ 많이 진행됐네요~ 해당 메시지는 인코딩 관련 오류인 것 같은데, 혹시 이름이 영어나 숫자로만 되어 있는 파일을 열어도 같은 문제가 발생하는지 확인해 보세요.

      그리고
      1. 저는 qt5 에 포함되어 있는 mingw 를 썼고,
      2. ui 파일 대신에 위젯과 레이아웃 함수를 써서 직접 화면을 구성했습니다. LVPlayer::initMenus() 와 LVPlayer::initWidgets() 함수가 그 일을 합니다.

      삭제
    8. 하.. 이게 뭐하는건지 모르겠네요 하하하...
      저 위에거는 해결이 또 됐습니다...

      QFileDialog 로 동영상 가져오는 것이 안됩니다.






      main stream debug: (path: C:\work\QT_project\build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug\????????????????????????????????????????????????????)
      main stream debug: looking for access module matching "file": 27 candidates
      filesystem stream error: cannot open file C:\work\QT_project\build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug\???????????????????????????????????????????????????? (No such file or directory)
      main stream debug: no access modules matched
      main input error: 입력을 열 수 없습니다
      main input error: VLC에서 'file:///C:/work/QT_project/build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug/%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD' MRL을 열 수 없습니다. 자세한 내용은 로그를 확인하세요.
      main audio output debug: removing module "directsound"
      main libvlc debug: exiting
      main libvlc debug: no exit handler
      main libvlc debug: removing all interfaces
      main keystore debug: removing module "memory"
      [00d30cc0] mmdevice audio output error: cannot initialize COM (error 0x80010106)
      [02f3de08] filesystem stream error: cannot open file C:\work\QT_project\build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug\硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼硼 (No such file or directory)
      [02f5ddb8] main input error: 낅젰놁뒿덈떎
      [02f5ddb8] main input error: VLC먯꽌 'file:///C:/work/QT_project/build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug/%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD%DD' MRL놁뒿덈떎. 먯꽭댁슜€ 濡쒓렇瑜뺤씤섏꽭
      18:10:09: C:\work\QT_project\build-lvplayer-Desktop_Qt_5_12_11_MSVC2017_32bit-Debug\debug\lvplayer.exe exited with code 0


      왜 파일을 읽어올때
      'file:///C: ~~~
      ///이게 왜 3개인지, 이 부분을 수정할 수 있는 코드는 어디인지 여쭤보고 싶습니다. 위에질문들은 무시하셔도 됩니다!

      삭제
    9. 어 댓글 남겨주신걸 못봤네요.. 답변 정말로 감사드립니다. 영어로만 되어있는 avi나 mkv mp4 모두 에러가 납니다. 왜 파일 불러오는게 안될까요...

      삭제
    10. 일단, file:/// 은 정상입니다. UNIX 스타일 표시법을 Windows 에도 적용한 결과입니다.
      원본 파일이름이 무엇인가요? 파일 이름 부분만 저렇게 변환되는 게 이상하네요.

      삭제
    11. 파일 이름은 abc.mkv, 123.mp4, yolo.avi, 2-2_21091023.avi 등 다 해보았는데 똑같습니다..
      위에 댓글과 똑같은 에러 문구가 나오면서 mrl을 열수없습니다.
      라고 뜹니다.

      https://forum.videolan.org/viewtopic.php?t=104939
      https://github.com/caprica/vlcj-javafx-demo/issues/38
      https://stackoverflow.com/questions/15221855/vlc-media-open-failure
      https://forum.videolan.org/viewtopic.php?t=146741

      등이 구글링해서 비슷한 것 찾은건데.. 해결이 안됩니다.. 왜 파일이름을 못알아듣고 mrl이 뭔지... 도와주실 수 있나요....

      삭제
    12. 아아아 됐습니다!!!! mingw로 하니까 됩니다...흐그흫휴ㅠ규ㅠㅜㅜㅜ
      이 코드가 msvc에서는 안되나봐요.... 코드 감사드립니다.
      혹시 여기 코드에서 더 나아가서 진짜 vlc처럼 재생목록, 메타데이터, 아래 바, 시간 등등 나오게 하려면 어떻게 더 구글링하면 좋을지 알려주시면 감사하겠습니다!!

      제 질문들 답변해주셔서 정말 감사드립니다!

      삭제
    13. 아 그리고 혹시 LV플레이어에서 LV뜻이 뭔가요?
      계속 궁금했습니다...

      삭제
    14. 오~ 축하드려요~^^ 컴파일러 문제일 줄이야, 생각도 못했네요.
      VLC 기능에 대해서는 VLC 사이트에 있는 문서들을 꼭 읽어보세요. 그리고 뭐니 뭐니 해도 오픈 소스이니 소스를 들여다 보는 게 가장 좋겠죠. 그럼에도 가장 먼저 할 만한 것은 vlc sdk 의 헤더 파일을 보시고 어떤 기능이 있는지 보시는 것입니다.

      MRL 은 Media Resource Locator의 약자입니다. 자세한 것은 https://wiki.videolan.org/Media_resource_locator/ 를 보세요.

      LV 는 별 뜻 없답니다. libvlc 약자예요. ^^

      계속 개량하셔서 멋진 동영상 재생기가 나오기를 기대하겠습니다~

      삭제
  2. 와우 자세한 설명까지.. 감사합니다 엄청 정성이 담긴글이네요

    답글삭제
  3. 안녕하세요 문의 사항이 있어 글을 남깁니다.
    임베디드 리눅스 환경에서 vlc를 사용하려고 하는데요
    혹시 x11이나 wayland 환경이 아닌 상태에서도 vlc를 사용할 수 있나요?
    libvlc_media_player_set_xwindow(_vlc, _movieWidget->winId());
    위의 함수를 보니 xwindow 환경에서만 사용 가능한것 같아서요
    두서 없는 질문 죄송합니다.
    그리고 감사합니다.

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷