diff options
-rw-r--r-- | vrplayer/README.txt | 13 | ||||
-rw-r--r-- | vrplayer/decoder.cpp | 72 | ||||
-rw-r--r-- | vrplayer/decoder.h | 39 | ||||
-rw-r--r-- | vrplayer/decoderthread.cpp | 217 | ||||
-rw-r--r-- | vrplayer/decoderthread.h | 72 | ||||
-rw-r--r-- | vrplayer/main.cpp | 11 | ||||
-rw-r--r-- | vrplayer/mainwindow.cpp | 326 | ||||
-rw-r--r-- | vrplayer/mainwindow.h | 82 | ||||
-rw-r--r-- | vrplayer/mainwindow.ui | 68 | ||||
-rw-r--r-- | vrplayer/vrplayer.pro | 30 | ||||
-rw-r--r-- | xrdpvr/xrdpvr.c | 138 | ||||
-rw-r--r-- | xrdpvr/xrdpvr.h | 17 | ||||
-rw-r--r-- | xrdpvr/xrdpvr_internal.h | 28 |
13 files changed, 1058 insertions, 55 deletions
diff --git a/vrplayer/README.txt b/vrplayer/README.txt new file mode 100644 index 00000000..91a911fa --- /dev/null +++ b/vrplayer/README.txt @@ -0,0 +1,13 @@ + +A QT based media player that runs on a RDP server and +redirects audio/video to the client where it is decoded +and rendered locally + +To build vrplayer, installl QT 4.x , then run + +qmake +make + +To run vrplayer, include xrdpapi/.libs and xrdpvr/.libs in +your LD_LIBRARY_PATH + diff --git a/vrplayer/decoder.cpp b/vrplayer/decoder.cpp new file mode 100644 index 00000000..b1741997 --- /dev/null +++ b/vrplayer/decoder.cpp @@ -0,0 +1,72 @@ +#include "decoder.h" + +Decoder::Decoder(QObject *parent) : + QObject(parent) +{ + channel = NULL; +} + +/* + * inititialize the decoder + * + * @return 0 on success, -1 on failure + *****************************************************************************/ +int Decoder::init(QString filename) +{ + if (channel) + return -1; + + /* open a virtual channel and connect to remote client */ + channel = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, "xrdpvr", 0); + if (channel == NULL) + { + qDebug() << "WTSVirtualChannelOpenEx() failed\n"; + return -1; + } + + /* initialize the player */ + if (xrdpvr_init_player(channel, 101, filename.toAscii().data())) + { + fprintf(stderr, "failed to initialize the player\n"); + return -1; + } + +#if 1 + sleep(1); + qDebug() << "sleeping 1"; + xrdpvr_set_geometry(channel, 101, mainWindowGeometry.x(), + mainWindowGeometry.y(), mainWindowGeometry.width(), + mainWindowGeometry.height()); + qDebug() << "set geometry to:" << mainWindowGeometry.x() << + "" << mainWindowGeometry.y() << + "" << mainWindowGeometry.width() << + "" << mainWindowGeometry.height(); +#endif + + /* send compressed media data to client; client will decompress */ + /* the media and play it locally */ + xrdpvr_play_media(channel, 101, filename.toAscii().data()); + + /* perform clean up */ + xrdpvr_deinit_player(channel, 101); + + WTSVirtualChannelClose(channel); + + return 0; +} + +void Decoder::onGeometryChanged(QRect *g) +{ +#if 1 + mainWindowGeometry.setX(g->x()); + mainWindowGeometry.setY(g->y()); + mainWindowGeometry.setWidth(g->width()); + mainWindowGeometry.setHeight(g->height()); +#else + if (!channel) + return; + + xrdpvr_set_geometry(channel, 101, g->x(), g->y(), g->width(), g->height()); + qDebug() << "sent geometry"; +#endif +} diff --git a/vrplayer/decoder.h b/vrplayer/decoder.h new file mode 100644 index 00000000..34b152e7 --- /dev/null +++ b/vrplayer/decoder.h @@ -0,0 +1,39 @@ +#ifndef DECODER_H +#define DECODER_H + +#include <QObject> +#include <QDebug> +#include <QRect> + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include <stdint.h> +#endif + +#include <libavformat/avformat.h> +#include <xrdpapi.h> +#include <xrdpvr.h> /* LK_TODO is this required? */ + +class Decoder : public QObject +{ + Q_OBJECT +public: + explicit Decoder(QObject *parent = 0); + int init(QString filename); + //int deinit(); + //int setWindow(QRectangle rect); + +private: + void *channel; + QRect mainWindowGeometry; + +signals: + +public slots: + void onGeometryChanged(QRect *geometry); +}; + +#endif // DECODER_H diff --git a/vrplayer/decoderthread.cpp b/vrplayer/decoderthread.cpp new file mode 100644 index 00000000..2ee7a524 --- /dev/null +++ b/vrplayer/decoderthread.cpp @@ -0,0 +1,217 @@ +#include "decoderthread.h" + +DecoderThread::DecoderThread() +{ + vsi = NULL; + channel = NULL; + geometry.setX(0); + geometry.setY(0); + geometry.setWidth(0); + geometry.setHeight(0); + stream_id = 101; + elapsedTime = 0; + la_seekPos = 0; +} + +void DecoderThread::run() +{ + int64_t start_time; + int64_t duration; + + /* TODO what happens if we get called a 2nd time while we are still running */ + + /* need a media file */ + if (filename.length() == 0) + { + emit on_decoderErrorMsg("No media file", + "Please select a media file to play"); + return; + } + + /* connect to remote client */ + if (openVirtualChannel()) + return; + + vsi = (VideoStateInfo *) av_mallocz(sizeof(VideoStateInfo)); + if (vsi == NULL) + { + emit on_decoderErrorMsg("Resource error", + "Memory allocation failed, system out of memory"); + return; + } + + /* register all formats/codecs */ + av_register_all(); + + if (sendMetadataFile()) + return; + + if (sendVideoFormat()) + return; + + if (sendAudioFormat()) + return; + + if (sendGeometry()) + return; + + xrdpvr_play_media(channel, 101, filename.toAscii().data()); + + xrdpvr_get_media_duration(&start_time, &duration); + emit on_mediaDurationInSeconds(duration); + + qDebug() << "start_time=" << start_time << " duration=" << duration; + + while (xrdpvr_play_frame(channel, 101) == 0) + { + if (elapsedTime == 0) + elapsedTime = av_gettime(); + + /* time elapsed in 1/100th sec units since play started */ + emit on_elapsedtime((av_gettime() - elapsedTime) / 10000); + + mutex.lock(); + if (la_seekPos) + { + qDebug() << "seeking to" << la_seekPos; + xrdpvr_seek_media(la_seekPos, 0); + elapsedTime = av_gettime() - la_seekPos * 1000000; + la_seekPos = 0; + } + mutex.unlock(); + } + + /* perform clean up */ + xrdpvr_deinit_player(channel, 101); + + /* clean up resources */ + closeVirtualChannel(); + if (vsi) + av_free(vsi); +} + +void DecoderThread::on_geometryChanged(int x, int y, int width, int height) +{ + geometry.setX(x); + geometry.setY(y); + geometry.setWidth(width); + geometry.setHeight(height); + +#if 0 + qDebug() << "decoderThread:signal" << + "" << geometry.x() << + "" << geometry.y() << + "" << geometry.width() << + "" << geometry.height(); +#endif + + if (channel) + { + xrdpvr_set_geometry(channel, 101, geometry.x(), geometry.y(), + geometry.width(), geometry.height()); + } +} + +void DecoderThread::on_mediaSeek(int value) +{ + mutex.lock(); + la_seekPos = value; + mutex.unlock(); +} + +void DecoderThread::setFilename(QString filename) +{ + this->filename = filename; +} + +/** + * @brief Open a virtual connection to remote client + * + * @return 0 on success, -1 on failure + ******************************************************************************/ +int DecoderThread::openVirtualChannel() +{ + /* is channel already open? */ + if (channel) + return -1; + + /* open a virtual channel and connect to remote client */ + channel = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, "xrdpvr", 0); + if (channel == NULL) + { + emit on_decoderErrorMsg("Connection failure", + "Error connecting to remote client"); + return -1; + } + return 0; +} + +int DecoderThread::closeVirtualChannel() +{ + /* channel must be opened first */ + if (!channel) + return -1; + + WTSVirtualChannelClose(channel); + return 0; +} + +/** + * @brief this is a temp hack while we figure out how to set up the right + * context for avcodec_decode_video2() on the server side; the workaround + * is to send the first 1MB of the media file to the server end which + * reads this file and sets up its context + * + * @return 0 on success, -1 on failure + ******************************************************************************/ +int DecoderThread::sendMetadataFile() +{ + if (xrdpvr_create_metadata_file(channel, filename.toAscii().data())) + { + emit on_decoderErrorMsg("I/O Error", + "An error occurred while sending data to remote client"); + return -1; + } + + return 0; +} + +int DecoderThread::sendVideoFormat() +{ + if (xrdpvr_set_video_format(channel, stream_id)) + { + emit on_decoderErrorMsg("I/O Error", + "Error sending video format to remote client"); + return -1; + } + + return 0; +} + +int DecoderThread::sendAudioFormat() +{ + if (xrdpvr_set_audio_format(channel, stream_id)) + { + emit on_decoderErrorMsg("I/O Error", + "Error sending audio format to remote client"); + return -1; + } + + return 0; +} + +int DecoderThread::sendGeometry() +{ + int rv; + + rv = xrdpvr_set_geometry(channel, stream_id, geometry.x(), geometry.y(), + geometry.width(), geometry.height()); + + if (rv) + { + emit on_decoderErrorMsg("I/O Error", + "Error sending screen geometry to remote client"); + return -1; + } + return 0; +} diff --git a/vrplayer/decoderthread.h b/vrplayer/decoderthread.h new file mode 100644 index 00000000..0f8a1c12 --- /dev/null +++ b/vrplayer/decoderthread.h @@ -0,0 +1,72 @@ +#ifndef DECODERTHREAD_H +#define DECODERTHREAD_H + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include <stdint.h> +#endif + +#include <QThread> +#include <QDebug> +#include <QString> +#include <QRect> +#include <QMutex> + +#include <xrdpapi.h> +#include <xrdpvr.h> + +/* ffmpeg related stuff */ +extern "C" +{ + #include <libavformat/avformat.h> + #include <libavcodec/avcodec.h> +} + +class DecoderThread : public QThread +{ + Q_OBJECT + +public: + DecoderThread(); + void setFilename(QString filename); + +public slots: + void on_geometryChanged(int x, int y, int width, int height); + void on_mediaSeek(int value); + +protected: + void run(); + +private: + typedef struct _VideoStateInfo + { + AVFormatContext *pFormatCtx; + } VideoStateInfo; + + VideoStateInfo *vsi; + QString filename; + void *channel; + int stream_id; + QRect geometry; + int64_t elapsedTime; /* elapsed time in usecs since play started */ + QMutex mutex; + int64_t la_seekPos; /* locked access; must hold mutex */ + + int openVirtualChannel(); + int closeVirtualChannel(); + int sendMetadataFile(); + int sendVideoFormat(); + int sendAudioFormat(); + int sendGeometry(); + +signals: + void on_progressUpdate(int percent); + void on_decoderErrorMsg(QString title, QString msg); + void on_mediaDurationInSeconds(int duration); + void on_elapsedtime(int val); /* in hundredth of a sec */ +}; + +#endif // DECODERTHREAD_H diff --git a/vrplayer/main.cpp b/vrplayer/main.cpp new file mode 100644 index 00000000..d951345f --- /dev/null +++ b/vrplayer/main.cpp @@ -0,0 +1,11 @@ +#include <QtGui/QApplication> +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/vrplayer/mainwindow.cpp b/vrplayer/mainwindow.cpp new file mode 100644 index 00000000..7f41eee9 --- /dev/null +++ b/vrplayer/mainwindow.cpp @@ -0,0 +1,326 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +/* + * TODO + * o should we use tick marks in QSlider? + */ + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + acceptSliderMove = false; + decoderThread = new DecoderThread(); + setupUI(); + +/* LK_TODO */ +#if 0 + decoder = new Decoder(this); + connect(this, SIGNAL(onGeometryChanged(int, int, int, int)), + decoder, SLOT(onGeometryChanged(int, int, int, int))); +#endif + + /* register for signals/slots with decoderThread */ + connect(this, SIGNAL(on_geometryChanged(int,int,int,int)), + decoderThread, SLOT(on_geometryChanged(int,int,int,int))); + + connect(decoderThread, SIGNAL(on_elapsedtime(int)), + this, SLOT(on_elapsedTime(int))); + + connect(decoderThread, SIGNAL(on_decoderErrorMsg(QString, QString)), + this, SLOT(on_decoderError(QString, QString))); + + connect(decoderThread, SIGNAL(on_mediaDurationInSeconds(int)), + this, SLOT(on_mediaDurationInSeconds(int))); + + connect(this, SIGNAL(on_mediaSeek(int)), decoderThread, SLOT(on_mediaSeek(int))); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + int rv; + + rv = QMessageBox::question(this, "Closing application", + "Do you really want to close vrplayer?", + QMessageBox::Yes | QMessageBox::No); + + if (rv == QMessageBox::No) + { + event->ignore(); + return; + } + decoderThread->exit(0); + event->accept(); +} + +void MainWindow::resizeEvent(QResizeEvent *e) +{ + QRect rect; + + getVdoGeometry(&rect); + emit on_geometryChanged(rect.x(), rect.y(), rect.width(), rect.height()); +} + +void MainWindow::moveEvent(QMoveEvent *e) +{ + QRect rect; + + getVdoGeometry(&rect); + emit on_geometryChanged(rect.x(), rect.y(), rect.width(), rect.height()); +} + +void MainWindow::setupUI() +{ + /* setup area to display video */ + lblVideo = new QLabel(); + lblVideo->setMinimumWidth(320); + lblVideo->setMinimumHeight(200); + QPalette palette = lblVideo->palette(); + palette.setColor(lblVideo->backgroundRole(), Qt::black); + palette.setColor(lblVideo->foregroundRole(), Qt::black); + lblVideo->setAutoFillBackground(true); + lblVideo->setPalette(palette); + hboxLayoutTop = new QHBoxLayout; + hboxLayoutTop->addWidget(lblVideo); + + /* setup label to display current pos in media */ + lblCurrentPos = new QLabel("00:00:00"); + lblCurrentPos->setMinimumHeight(20); + lblCurrentPos->setMaximumHeight(20); + + /* setup slider to seek into media */ + slider = new QSlider(); + slider->setOrientation(Qt::Horizontal); + slider->setMinimumHeight(20); + slider->setMaximumHeight(20); + connect(slider, SIGNAL(actionTriggered(int)), this, SLOT(on_sliderActionTriggered(int))); + connect(slider, SIGNAL(valueChanged(int)), this, SLOT(on_sliderValueChanged(int))); + + /* setup label to display media duration */ + lblDuration = new QLabel("00:00:00"); + lblDuration->setMinimumHeight(20); + lblDuration->setMaximumHeight(20); + + /* add above three widgets to mid layout */ + hboxLayoutMiddle = new QHBoxLayout; + hboxLayoutMiddle->addWidget(lblCurrentPos); + hboxLayoutMiddle->addWidget(slider); + hboxLayoutMiddle->addWidget(lblDuration); + + /* setup play button */ + btnPlay = new QPushButton("P"); + btnPlay->setMinimumHeight(40); + btnPlay->setMaximumHeight(40); + btnPlay->setMinimumWidth(40); + btnPlay->setMaximumWidth(40); + connect(btnPlay, SIGNAL(clicked(bool)), this, SLOT(on_btnPlayClicked(bool))); + + /* setup stop button */ + btnStop = new QPushButton("S"); + btnStop->setMinimumHeight(40); + btnStop->setMaximumHeight(40); + btnStop->setMinimumWidth(40); + btnStop->setMaximumWidth(40); + + /* setup rewind button */ + btnRewind = new QPushButton("R"); + btnRewind->setMinimumHeight(40); + btnRewind->setMaximumHeight(40); + btnRewind->setMinimumWidth(40); + btnRewind->setMaximumWidth(40); + + /* add buttons to bottom panel */ + hboxLayoutBottom = new QHBoxLayout; + hboxLayoutBottom->addWidget(btnPlay); + hboxLayoutBottom->addWidget(btnStop); + hboxLayoutBottom->addWidget(btnRewind); + hboxLayoutBottom->addStretch(); + + /* add all three layouts to one vertical layout */ + vboxLayout = new QVBoxLayout; + vboxLayout->addLayout(hboxLayoutTop); + vboxLayout->addLayout(hboxLayoutMiddle); + vboxLayout->addLayout(hboxLayoutBottom); + + /* add all of them to central widget */ + window = new QWidget; + window->setLayout(vboxLayout); + this->setCentralWidget(window); +} + +void MainWindow::openMediaFile() +{ + /* TODO take last stored value from QSettings */ + + if (filename.length() == 0) + { + /* no previous selection - open user's home folder TODO */ + // TODO filename = QFileDialog::getOpenFileName(this, "Select Media File", "/"); + filename = QFileDialog::getOpenFileName(this, "Select Media File", "/home/lk/vbox_share"); + } + else + { + /* show last selected file */ + filename = QFileDialog::getOpenFileName(this, "Select Media File", + filename); + } + decoderThread->setFilename(filename); +} + +void MainWindow::getVdoGeometry(QRect *rect) +{ + int x = geometry().x() + lblVideo->geometry().x(); + + int y = pos().y() + lblVideo->geometry().y() + + ui->mainToolBar->geometry().height() * 4 + 10; + + rect->setX(x); + rect->setY(y); + rect->setWidth(lblVideo->geometry().width()); + rect->setHeight(lblVideo->geometry().height()); +} + +/******************************************************************************* + * actions and slots go here * + ******************************************************************************/ + +void MainWindow::on_actionOpen_Media_File_triggered() +{ + openMediaFile(); +} + +void MainWindow::on_actionExit_triggered() +{ + /* TODO: confirm app exit */ + this->close(); +} + +void MainWindow::on_actionPlay_Media_triggered() +{ + // LK_TODO do we need this? if yes, should be same as on_btnPlayClicked() +#if 1 + decoderThread->start(); +#else + if (!decoder) + return; + + decoder->init(filename); +#endif +} + +void MainWindow::on_decoderError(QString title, QString msg) +{ + QMessageBox::information(this, title, msg); +} + +void MainWindow::on_btnPlayClicked(bool flag) +{ + if (filename.length() == 0) + openMediaFile(); + + decoderThread->start(); +} + +void MainWindow::on_mediaDurationInSeconds(int duration) +{ + int hours = 0; + int minutes = 0; + int secs = 0; + char buf[20]; + + /* setup progress bar */ + slider->setMinimum(0); + slider->setMaximum(duration * 100); /* in hundredth of a sec */ + slider->setValue(0); + + /* convert from seconds to hours:minutes:seconds */ + hours = duration / 3600; + if (hours) + duration -= (hours * 3600); + + minutes = duration / 60; + if (minutes) + duration -= minutes * 60; + + secs = duration; + + sprintf(buf, "%.2d:%.2d:%.2d", hours, minutes, secs); + lblDuration->setText(QString(buf)); +} + +/** + * time elapsed in 1/100th sec units since play started + ******************************************************************************/ +void MainWindow::on_elapsedTime(int val) +{ + int hours = 0; + int minutes = 0; + int secs = 0; + int duration = val / 100; + char buf[20]; + + /* if slider bar is down, do not update */ + if (slider->isSliderDown()) + return; + + /* update progress bar */ + slider->setSliderPosition(val); + + /* convert from seconds to hours:minutes:seconds */ + hours = duration / 3600; + if (hours) + duration -= (hours * 3600); + + minutes = duration / 60; + if (minutes) + duration -= minutes * 60; + + secs = duration; + + /* update current position in progress bar */ + sprintf(buf, "%.2d:%.2d:%.2d", hours, minutes, secs); + lblCurrentPos->setText(QString(buf)); +} + +void MainWindow::on_sliderValueChanged(int value) +{ + if (acceptSliderMove) + { + acceptSliderMove = false; + emit on_mediaSeek(value / 100); + } +} + +void MainWindow::on_sliderActionTriggered(int action) +{ + switch (action) + { + case QAbstractSlider::SliderPageStepAdd: + acceptSliderMove = true; + break; + + case QAbstractSlider::SliderPageStepSub: + acceptSliderMove = true; + break; + + case QAbstractSlider::SliderMove: + if (slider->isSliderDown()) + acceptSliderMove = true; + break; + } +} + +#if 1 +// LK_TODO delete this +void MainWindow::mouseMoveEvent(QMouseEvent *e) +{ + //qDebug() << "mouseMoveEvent: x=" << e->globalX() << "y=" << e->globalY(); +} +#endif diff --git a/vrplayer/mainwindow.h b/vrplayer/mainwindow.h new file mode 100644 index 00000000..ed392ea2 --- /dev/null +++ b/vrplayer/mainwindow.h @@ -0,0 +1,82 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QFileDialog> +#include <QDebug> +#include <QMessageBox> +#include <QCloseEvent> +#include <QMoveEvent> +#include <QPoint> +#include <QRect> +#include <QLabel> +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QPushButton> +#include <QSlider> + +#include "decoder.h" +#include "decoderthread.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void on_actionOpen_Media_File_triggered(); + void on_actionExit_triggered(); + void on_actionPlay_Media_triggered(); + void on_decoderError(QString title, QString msg); + void on_btnPlayClicked(bool flag); + void on_mediaDurationInSeconds(int duration); + void on_elapsedTime(int secs); + void on_sliderActionTriggered(int value); + void on_sliderValueChanged(int value); + +signals: + void on_geometryChanged(int x, int y, int widht, int height); + void on_mediaSeek(int value); + +protected: + void resizeEvent(QResizeEvent *e); + void closeEvent(QCloseEvent *e); + void moveEvent(QMoveEvent *e); + void mouseMoveEvent(QMouseEvent *e); + +private: + Ui::MainWindow *ui; + + QString filename; + Decoder *decoder; + DecoderThread *decoderThread; + + /* for UI */ + QLabel *lblCurrentPos; + QLabel *lblDuration; + QLabel *lblVideo; + QHBoxLayout *hboxLayoutTop; + QHBoxLayout *hboxLayoutMiddle; + QHBoxLayout *hboxLayoutBottom; + QVBoxLayout *vboxLayout; + QPushButton *btnPlay; + QPushButton *btnStop; + QPushButton *btnRewind; + QSlider *slider; + QWidget *window; + bool acceptSliderMove; + + /* private methods */ + void setupUI(); + void openMediaFile(); + void getVdoGeometry(QRect *rect); +}; + +#endif // MAINWINDOW_H diff --git a/vrplayer/mainwindow.ui b/vrplayer/mainwindow.ui new file mode 100644 index 00000000..7d4a81c6 --- /dev/null +++ b/vrplayer/mainwindow.ui @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralWidget"/> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionOpen_Media_File"/> + <addaction name="actionPlay_Media"/> + <addaction name="separator"/> + <addaction name="actionExit"/> + </widget> + <addaction name="menuFile"/> + </widget> + <widget class="QToolBar" name="mainToolBar"> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + </widget> + <widget class="QStatusBar" name="statusBar"/> + <action name="actionOpen_Media_File"> + <property name="text"> + <string>Open Media File</string> + </property> + </action> + <action name="actionExit"> + <property name="text"> + <string>Exit application</string> + </property> + <property name="toolTip"> + <string>Exit application</string> + </property> + </action> + <action name="actionPlay_Media"> + <property name="text"> + <string>Play Media</string> + </property> + </action> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/vrplayer/vrplayer.pro b/vrplayer/vrplayer.pro new file mode 100644 index 00000000..9aac8aff --- /dev/null +++ b/vrplayer/vrplayer.pro @@ -0,0 +1,30 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2012-11-13T11:52:36 +# +#------------------------------------------------- + +QT += core gui + +TARGET = vrplayer +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + decoder.cpp \ + decoderthread.cpp + +HEADERS += mainwindow.h \ + decoder.h \ + decoderthread.h + +FORMS += mainwindow.ui + +# added by LK +INCLUDEPATH += ../xrdpvr +INCLUDEPATH += ../xrdpapi + +LIBS += -L../xrdpvr/.libs -lxrdpvr +LIBS += -L../xrdpapi/.libs -lxrdpapi +LIBS += -L/usr/lib/x86_64-linux-gnu -lavformat -lavcodec -lavutil diff --git a/xrdpvr/xrdpvr.c b/xrdpvr/xrdpvr.c index 6293818e..66dfea5a 100644 --- a/xrdpvr/xrdpvr.c +++ b/xrdpvr/xrdpvr.c @@ -25,6 +25,8 @@ /* globals */ PLAYER_STATE_INFO g_psi; +int g_video_index = -1; +int g_audio_index = -1; /** * initialize the media player @@ -129,10 +131,6 @@ xrdpvr_deinit_player(void *channel, int stream_id) int xrdpvr_play_media(void *channel, int stream_id, char *filename) { - AVPacket av_pkt; - - int video_index = -1; - int audio_index = -1; int i; /* register all available fileformats and codecs */ @@ -161,19 +159,19 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename) for (i = 0; i < g_psi.p_format_ctx->nb_streams; i++) { if (g_psi.p_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && - video_index < 0) + g_video_index < 0) { - video_index = i; + g_video_index = i; } if (g_psi.p_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && - audio_index < 0) + g_audio_index < 0) { - audio_index = i; + g_audio_index = i; } } - if ((audio_index < 0) && (video_index < 0)) + if ((g_audio_index < 0) && (g_video_index < 0)) { /* close file and return with error */ printf("ERROR: no audio/video stream found in %s\n", filename); @@ -181,12 +179,12 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename) return -1; } - g_psi.audio_stream_index = audio_index; - g_psi.video_stream_index = video_index; + g_psi.audio_stream_index = g_audio_index; + g_psi.video_stream_index = g_video_index; /* get pointers to codex contexts for both streams */ - g_psi.p_audio_codec_ctx = g_psi.p_format_ctx->streams[audio_index]->codec; - g_psi.p_video_codec_ctx = g_psi.p_format_ctx->streams[video_index]->codec; + g_psi.p_audio_codec_ctx = g_psi.p_format_ctx->streams[g_audio_index]->codec; + g_psi.p_video_codec_ctx = g_psi.p_format_ctx->streams[g_video_index]->codec; /* find decoder for audio stream */ g_psi.p_audio_codec = avcodec_find_decoder(g_psi.p_audio_codec_ctx->codec_id); @@ -220,32 +218,99 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename) return -1; } - while (av_read_frame(g_psi.p_format_ctx, &av_pkt) >= 0) + return 0; +} + +int xrdpvr_play_frame(void *channel, int stream_id) +{ + AVPacket av_pkt; + + printf("xrdpvr_play_frame: entered\n"); + + if (av_read_frame(g_psi.p_format_ctx, &av_pkt) < 0) { - if (av_pkt.stream_index == audio_index) - { - xrdpvr_send_audio_data(channel, stream_id, av_pkt.size, av_pkt.data); - usleep(1000 * 1); - } - else if (av_pkt.stream_index == video_index) - { - xrdpvr_send_video_data(channel, stream_id, av_pkt.size, av_pkt.data); - usleep(1000 * 40); // was 50 - } + printf("xrdpvr_play_frame: av_read_frame failed\n"); + return -1; + } + + if (av_pkt.stream_index == g_audio_index) + { + xrdpvr_send_audio_data(channel, stream_id, av_pkt.size, av_pkt.data); + usleep(1000 * 1); + } + else if (av_pkt.stream_index == g_video_index) + { + xrdpvr_send_video_data(channel, stream_id, av_pkt.size, av_pkt.data); + usleep(1000 * 40); // was 50 } av_free_packet(&av_pkt); + return 0; +} + +int +xrdpvr_seek_media(int64_t pos, int backward) +{ + int64_t seek_target; + int seek_flag; + + printf("xrdpvr_seek_media() entered\n"); + seek_flag = (backward) ? AVSEEK_FLAG_BACKWARD : 0; + + seek_target = av_rescale_q(pos * AV_TIME_BASE, + AV_TIME_BASE_Q, + g_psi.p_format_ctx->streams[g_video_index]->time_base); + + + if(av_seek_frame(g_psi.p_format_ctx, g_video_index, seek_target, seek_flag) < 0) + { + printf("media seek error\n"); + return -1; + } + printf("xrdpvr_seek_media: success\n"); return 0; } -/****************************************************************************** - * - * code below this is local to this file and cannot be accessed externally; - * this code communicates with the xrdpvr plugin in NeutrinoRDP; - * NeutrinoRDP is a fork of FreeRDP 1.0.1 - * - *****************************************************************************/ +void +xrdpvr_get_media_duration(int64_t *start_time, int64_t *duration) +{ + *start_time = g_psi.p_format_ctx->start_time / AV_TIME_BASE; + *duration = g_psi.p_format_ctx->duration / AV_TIME_BASE; +} + +int +xrdpvr_set_geometry(void *channel, int stream_id, int xpos, int ypos, int width, int height) +{ + STREAM *s; + char *cptr; + int rv; + int len; + +printf("xrdpvr_set_geometry: entered; x=%d y=%d\n", xpos, ypos); + + stream_new(s, MAX_PDU_SIZE); + + stream_ins_u32_le(s, 0); /* number of bytes to follow */ + stream_ins_u32_le(s, CMD_SET_GEOMETRY); + stream_ins_u32_le(s, stream_id); + stream_ins_u32_le(s, xpos); + stream_ins_u32_le(s, ypos); + stream_ins_u32_le(s, width); + stream_ins_u32_le(s, height); + + /* insert number of bytes in stream */ + len = stream_length(s) - 4; + cptr = s->p; + s->p = s->data; + stream_ins_u32_le(s, len); + s->p = cptr; + + /* write data to virtual channel */ + rv = xrdpvr_write_to_client(channel, s); + stream_free(s); + return rv; +} /** * set video format @@ -255,7 +320,7 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename) * * @return 0 on success, -1 on error *****************************************************************************/ -static int +int xrdpvr_set_video_format(void *channel, uint32_t stream_id) { STREAM *s; @@ -290,7 +355,7 @@ xrdpvr_set_video_format(void *channel, uint32_t stream_id) * * @return 0 on success, -1 on error *****************************************************************************/ -static int +int xrdpvr_set_audio_format(void *channel, uint32_t stream_id) { STREAM *s; @@ -327,7 +392,7 @@ xrdpvr_set_audio_format(void *channel, uint32_t stream_id) * * @return 0 on success, -1 on error *****************************************************************************/ -static int +int xrdpvr_send_video_data(void *channel, uint32_t stream_id, uint32_t data_len, uint8_t *data) { STREAM *s; @@ -371,7 +436,7 @@ xrdpvr_send_video_data(void *channel, uint32_t stream_id, uint32_t data_len, uin * * @return 0 on success, -1 on error *****************************************************************************/ -static int +int xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len, uint8_t *data) { STREAM *s; @@ -408,7 +473,7 @@ xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len, uin * * @return 0 on success, -1 on error *****************************************************************************/ -static int +int xrdpvr_create_metadata_file(void *channel, char *filename) { STREAM *s; @@ -518,3 +583,4 @@ xrdpvr_write_to_client(void *channel, STREAM *s) usleep(1000 * 3); } } + diff --git a/xrdpvr/xrdpvr.h b/xrdpvr/xrdpvr.h index 88e30e76..4368db1f 100644 --- a/xrdpvr/xrdpvr.h +++ b/xrdpvr/xrdpvr.h @@ -23,13 +23,24 @@ #ifndef __XRDPVR_H__ #define __XRDPVR_H__ +#include <stdint.h> + #ifdef __cplusplus extern "C" { #endif -int xrdpvr_init_player(void *channel, int stream_id, char *filename); -int xrdpvr_deinit_player(void *channel, int stream_id); -int xrdpvr_play_media(void *channel, int stream_id, char *filename); +int xrdpvr_init_player(void *channel, int stream_id, char *filename); +int xrdpvr_deinit_player(void *channel, int stream_id); +int xrdpvr_play_media(void *channel, int stream_id, char *filename); +int xrdpvr_set_geometry(void *channel, int stream_id, int xpos, int ypos, int width, int height); +int xrdpvr_set_video_format(void *channel, uint32_t stream_id); +int xrdpvr_set_audio_format(void *channel, uint32_t stream_id); +int xrdpvr_send_video_data(void *channel, uint32_t stream_id, uint32_t data_len, uint8_t *data); +int xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len, uint8_t *data); +int xrdpvr_create_metadata_file(void *channel, char *filename); +int xrdpvr_play_frame(void *channel, int stream_id); +void xrdpvr_get_media_duration(int64_t *start_time, int64_t *duration); +int xrdpvr_seek_media(int64_t pos, int backward); #ifdef __cplusplus } diff --git a/xrdpvr/xrdpvr_internal.h b/xrdpvr/xrdpvr_internal.h index 7efde4ee..995306c3 100644 --- a/xrdpvr/xrdpvr_internal.h +++ b/xrdpvr/xrdpvr_internal.h @@ -38,6 +38,7 @@ #define CMD_CLOSE_META_DATA_FILE 6 #define CMD_WRITE_META_DATA 7 #define CMD_DEINIT_XRDPVR 8 +#define CMD_SET_GEOMETRY 9 /* max number of bytes we can send in one pkt */ #define MAX_PDU_SIZE 1600 @@ -60,7 +61,7 @@ typedef struct stream * @param _s stream to create and init * @param _len number of bytes to store in stream ******************************************************************************/ -#define stream_new(_s, _len) \ +#define stream_new(_s, _len) \ do \ { \ (_s) = (STREAM *) calloc(1, sizeof(STREAM)); \ @@ -73,7 +74,7 @@ typedef struct stream /** * create a stream from an existing buffer ******************************************************************************/ -#define stream_from_buffer(_s, _buf, _buf_len) \ +#define stream_from_buffer(_s, _buf, _buf_len) \ do \ { \ (_s) = (STREAM *) calloc(1, sizeof(STREAM)); \ @@ -89,7 +90,7 @@ typedef struct stream * * @param _s the stream whose resources are to be released ******************************************************************************/ -#define stream_free(_s) \ +#define stream_free(_s) \ do \ { \ if (!(_s)->from_buf) \ @@ -104,14 +105,14 @@ typedef struct stream #define stream_length(_s) (int) ((_s)->p - (_s)->data) /** insert a 8 bit value into stream */ -#define stream_ins_u8(_s, _val) \ +#define stream_ins_u8(_s, _val) \ do \ { \ *(_s)->p++ = (unsigned char) (_val); \ } while(0) /** insert a 16 bit value into stream */ -#define stream_ins_u16_le(_s, _val) \ +#define stream_ins_u16_le(_s, _val) \ do \ { \ *(_s)->p++ = (unsigned char) ((_val) >> 0); \ @@ -119,7 +120,7 @@ typedef struct stream } while (0) /** insert a 32 bit value into stream */ -#define stream_ins_u32_le(_s, _val) \ +#define stream_ins_u32_le(_s, _val) \ do \ { \ *(_s)->p++ = (unsigned char) ((_val) >> 0); \ @@ -129,7 +130,7 @@ typedef struct stream } while (0) /** insert a 64 bit value into stream */ -#define stream_ins_u64_le(_s, _val) \ +#define stream_ins_u64_le(_s, _val) \ do \ { \ *(_s)->p++ = (unsigned char) ((_val) >> 0); \ @@ -143,7 +144,7 @@ typedef struct stream } while (0) /** insert array of chars into stream */ -#define stream_ins_byte_array(_s, _ba, _count) \ +#define stream_ins_byte_array(_s, _ba, _count) \ do \ { \ memcpy((_s)->p, (_ba), (_count)); \ @@ -151,14 +152,14 @@ typedef struct stream } while (0) /** extract a 8 bit value from stream */ -#define stream_ext_u8(_s, _v) \ +#define stream_ext_u8(_s, _v) \ do \ { \ (_v) = (u8) *(_s)->p++; \ } while (0) /** extract a 16 bit value from stream */ -#define stream_ext_u16_le(_s, _v) \ +#define stream_ext_u16_le(_s, _v) \ do \ { \ (_v) = (u16) ((_s)->p[1] << 8 | (_s)->p[0]); \ @@ -166,7 +167,7 @@ typedef struct stream } while (0) /** extract a 32 bit value from stream */ -#define stream_ext_u32_le(_s, _v) \ +#define stream_ext_u32_le(_s, _v) \ do \ { \ (_v) = (u32) ((_s)->p[3] << 24 | \ @@ -193,11 +194,6 @@ typedef struct _player_state_info } PLAYER_STATE_INFO; -static int xrdpvr_set_video_format(void *channel, uint32_t stream_id); -static int xrdpvr_set_audio_format(void *channel, uint32_t stream_id); -static int xrdpvr_send_video_data(void *channel, uint32_t stream_id, uint32_t data_len, uint8_t *data); -static int xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len, uint8_t *data); -static int xrdpvr_create_metadata_file(void *channel, char *filename); static int xrdpvr_write_to_client(void *channel, STREAM *s); #endif /* __XRDPVR_INTERNAL_H__ */ |