diff --git a/vrplayer/decoderthread.cpp b/vrplayer/decoderthread.cpp index 2ee7a524..e31b1aa3 100644 --- a/vrplayer/decoderthread.cpp +++ b/vrplayer/decoderthread.cpp @@ -1,8 +1,17 @@ #include "decoderthread.h" +/* + * TODO: + * o need to maintain aspect ratio while resizing + * o clicking in the middle of the slider bar shuld move the slider to the middle + * o need to be able to rewind the move when it is done playing + * o need to be able to load another move and play it w/o restarting player + * o pause button needs to work + * o need images for btns + */ + DecoderThread::DecoderThread() { - vsi = NULL; channel = NULL; geometry.setX(0); geometry.setY(0); @@ -10,16 +19,13 @@ DecoderThread::DecoderThread() geometry.setHeight(0); stream_id = 101; elapsedTime = 0; - la_seekPos = 0; + la_seekPos = -1; + videoTimer = NULL; + audioTimer = NULL; } 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) { @@ -27,89 +33,72 @@ void DecoderThread::run() "Please select a media file to play"); return; } +} - /* connect to remote client */ - if (openVirtualChannel()) - return; - - vsi = (VideoStateInfo *) av_mallocz(sizeof(VideoStateInfo)); - if (vsi == NULL) +void DecoderThread::startMediaPlay() +{ + MediaPacket *mediaPkt; + int is_video_frame; + int rv; + + /* setup video timer; each time this timer fires, it sends */ + /* one video pkt to the client then resets the callback duration */ + videoTimer = new QTimer; + connect(videoTimer, SIGNAL(timeout()), this, SLOT(videoTimerCallback())); + //videoTimer->start(1500); + + /* setup audio timer; does the same as above, but with audio pkts */ + audioTimer = new QTimer; + connect(audioTimer, SIGNAL(timeout()), this, SLOT(audioTimerCallback())); + //audioTimer->start(500); + + /* setup pktTimer; each time this timer fires, it reads AVPackets */ + /* and puts them into audio/video Queues */ + pktTimer = new QTimer; + connect(pktTimer, SIGNAL(timeout()), this, SLOT(pktTimerCallback())); + + while (1) { - 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()); + /* fill the audio/video queues with initial data; thereafter */ + /* data will be filled by pktTimerCallback() */ + if ((audioQueue.count() >= 3000) || (videoQueue.count() >= 3000)) + { + //pktTimer->start(50); - xrdpvr_get_media_duration(&start_time, &duration); - emit on_mediaDurationInSeconds(duration); + //videoTimer->start(1500); + //audioTimer->start(500); - qDebug() << "start_time=" << start_time << " duration=" << duration; + playVideo = new PlayVideo(NULL, &videoQueue, channel, 101); + playVideoThread = new QThread(this); + connect(playVideoThread, SIGNAL(started()), playVideo, SLOT(play())); + playVideo->moveToThread(playVideoThread); + playVideoThread->start(); - while (xrdpvr_play_frame(channel, 101) == 0) - { - if (elapsedTime == 0) - elapsedTime = av_gettime(); + playAudio = new PlayAudio(NULL, &audioQueue, channel, 101); + playAudioThread = new QThread(this); + connect(playAudioThread, SIGNAL(started()), playAudio, SLOT(play())); + playAudio->moveToThread(playAudioThread); + playAudioThread->start(); - /* time elapsed in 1/100th sec units since play started */ - emit on_elapsedtime((av_gettime() - elapsedTime) / 10000); + return; + } - mutex.lock(); - if (la_seekPos) + mediaPkt = new MediaPacket; + rv = xrdpvr_get_frame(&mediaPkt->av_pkt, + &is_video_frame, + &mediaPkt->delay_in_us); + if (rv < 0) { - qDebug() << "seeking to" << la_seekPos; - xrdpvr_seek_media(la_seekPos, 0); - elapsedTime = av_gettime() - la_seekPos * 1000000; - la_seekPos = 0; + /* looks like we reached end of file */ + break; } - 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 (is_video_frame) + videoQueue.enqueue(mediaPkt); + else + audioQueue.enqueue(mediaPkt); - if (channel) - { - xrdpvr_set_geometry(channel, 101, geometry.x(), geometry.y(), - geometry.width(), geometry.height()); - } + } /* end while (1) */ } void DecoderThread::on_mediaSeek(int value) @@ -117,6 +106,15 @@ void DecoderThread::on_mediaSeek(int value) mutex.lock(); la_seekPos = value; mutex.unlock(); + + qDebug() << "media seek value=" << value; + + /* pktTimer stops at end of media; need to restart it */ + if (!pktTimer->isActive()) + { + updateSlider(); + pktTimer->start(100); + } } void DecoderThread::setFilename(QString filename) @@ -124,94 +122,125 @@ 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() +void DecoderThread::stopPlayer() { - /* is channel already open? */ - if (channel) - return -1; + pktTimer->stop(); + audioQueue.clear(); + videoQueue.clear(); +} - /* 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; +void DecoderThread::pausePlayer() +{ + pktTimer->stop(); } -int DecoderThread::closeVirtualChannel() +void DecoderThread::resumePlayer() { - /* channel must be opened first */ - if (!channel) - return -1; + pktTimer->start(100); +} - WTSVirtualChannelClose(channel); - return 0; +void DecoderThread::close() +{ } -/** - * @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() +void DecoderThread::audioTimerCallback() { - if (xrdpvr_create_metadata_file(channel, filename.toAscii().data())) + MediaPacket *pkt; + int delayInMs; + + if (audioQueue.isEmpty()) { - emit on_decoderErrorMsg("I/O Error", - "An error occurred while sending data to remote client"); - return -1; + qDebug() << "audioTimerCallback: got empty"; + audioTimer->setInterval(100); + return; } - return 0; + pkt = audioQueue.dequeue(); + delayInMs = (int) ((float) pkt->delay_in_us / 1000.0); + send_audio_pkt(channel, 101, pkt->av_pkt); + delete pkt; + + //qDebug() << "audioTimerCallback: delay :" << delayInMs; + + audioTimer->setInterval(delayInMs); } -int DecoderThread::sendVideoFormat() +void DecoderThread::videoTimerCallback() { - if (xrdpvr_set_video_format(channel, stream_id)) + MediaPacket *pkt; + int delayInMs; + + if (videoQueue.isEmpty()) { - emit on_decoderErrorMsg("I/O Error", - "Error sending video format to remote client"); - return -1; + qDebug() << "videoTimerCallback: GOT EMPTY"; + videoTimer->setInterval(100); + return; } - return 0; + pkt = videoQueue.dequeue(); + delayInMs = (int) ((float) pkt->delay_in_us / 1000.0); + send_video_pkt(channel, 101, pkt->av_pkt); + delete pkt; + updateSlider(); + //qDebug() << "videoTimerCallback: delay :" << delayInMs; + videoTimer->setInterval(delayInMs); } -int DecoderThread::sendAudioFormat() +void DecoderThread::pktTimerCallback() { - if (xrdpvr_set_audio_format(channel, stream_id)) + MediaPacket *mediaPkt; + int is_video_frame; + int rv; + + while (1) { - emit on_decoderErrorMsg("I/O Error", - "Error sending audio format to remote client"); - return -1; - } + qDebug() << "pktTimerCallback: audioCount=" << audioQueue.count() << "videoCount=" << videoQueue.count(); +#if 1 + if ((audioQueue.count() >= 20) || (videoQueue.count() >= 20)) + return; +#else + if (videoQueue.count() >= 60) + return; +#endif + mediaPkt = new MediaPacket; + rv = xrdpvr_get_frame(&mediaPkt->av_pkt, + &is_video_frame, + &mediaPkt->delay_in_us); + if (rv < 0) + { + /* looks like we reached end of file */ + qDebug() << "###### looks like we reached EOF"; + pktTimer->stop(); + // LK_TODO set some flag so audio/video timer also stop when q is empty + return; + } - return 0; + if (is_video_frame) + videoQueue.enqueue(mediaPkt); + else + audioQueue.enqueue(mediaPkt); + } } -int DecoderThread::sendGeometry() +void DecoderThread::updateSlider() { - int rv; + if (elapsedTime == 0) + elapsedTime = av_gettime(); - rv = xrdpvr_set_geometry(channel, stream_id, geometry.x(), geometry.y(), - geometry.width(), geometry.height()); + /* time elapsed in 1/100th sec units since play started */ + emit on_elapsedtime((av_gettime() - elapsedTime) / 10000); - if (rv) + mutex.lock(); + if (la_seekPos >= 0) { - emit on_decoderErrorMsg("I/O Error", - "Error sending screen geometry to remote client"); - return -1; + qDebug() << "seeking to" << la_seekPos; + //audioTimer->stop(); + //videoTimer->stop(); + xrdpvr_seek_media(la_seekPos, 0); + elapsedTime = av_gettime() - la_seekPos * 1000000; + //audioTimer->start(10); + //videoTimer->start(10); + la_seekPos = -1; } - return 0; + mutex.unlock(); } diff --git a/vrplayer/decoderthread.h b/vrplayer/decoderthread.h index 0f8a1c12..e7116fa4 100644 --- a/vrplayer/decoderthread.h +++ b/vrplayer/decoderthread.h @@ -14,9 +14,14 @@ #include #include #include +#include +#include #include #include +#include +#include +#include /* ffmpeg related stuff */ extern "C" @@ -25,28 +30,34 @@ extern "C" #include } -class DecoderThread : public QThread +class DecoderThread : public QObject { Q_OBJECT public: + /* public methods */ DecoderThread(); + void setFilename(QString filename); + void stopPlayer(); + void pausePlayer(); + void resumePlayer(); + void oneTimeDeinit(); + void close(); + void run(); + void startMediaPlay(); 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; + /* private variables */ + QQueue audioQueue; + QQueue videoQueue; - VideoStateInfo *vsi; + QTimer *videoTimer; + QTimer *audioTimer; + QTimer *pktTimer; QString filename; void *channel; int stream_id; @@ -55,14 +66,26 @@ private: QMutex mutex; int64_t la_seekPos; /* locked access; must hold mutex */ - int openVirtualChannel(); - int closeVirtualChannel(); - int sendMetadataFile(); - int sendVideoFormat(); - int sendAudioFormat(); - int sendGeometry(); + PlayVideo *playVideo; + QThread *playVideoThread; + PlayAudio *playAudio; + QThread *playAudioThread; + + /* private functions */ + int sendMetadataFile(); + int sendAudioFormat(); + int sendVideoFormat(); + int sendGeometry(); + void updateSlider(); + +private slots: + /* private slots */ + void audioTimerCallback(); + void videoTimerCallback(); + void pktTimerCallback(); signals: + /* private signals */ void on_progressUpdate(int percent); void on_decoderErrorMsg(QString title, QString msg); void on_mediaDurationInSeconds(int duration); diff --git a/vrplayer/demuxmedia.cpp b/vrplayer/demuxmedia.cpp new file mode 100644 index 00000000..7a13619e --- /dev/null +++ b/vrplayer/demuxmedia.cpp @@ -0,0 +1,116 @@ +#include "demuxmedia.h" + +DemuxMedia::DemuxMedia(QObject *parent, QQueue *audioQueue, + QQueue *videoQueue, void *channel, int stream_id) : + QObject(parent) +{ + this->audioQueue = audioQueue; + this->videoQueue = videoQueue; + this->channel = channel; + this->stream_id = stream_id; + this->threadsStarted = false; + this->vcrFlag = 0; + + playAudio = new PlayAudio(NULL, audioQueue, &sendMutex, channel, 101); + playAudioThread = new QThread(this); + connect(playAudioThread, SIGNAL(started()), playAudio, SLOT(play())); + playAudio->moveToThread(playAudioThread); + + playVideo = new PlayVideo(NULL, videoQueue, &sendMutex, channel, 101); + playVideoThread = new QThread(this); + connect(playVideoThread, SIGNAL(started()), playVideo, SLOT(play())); + playVideo->moveToThread(playVideoThread); +} + +void DemuxMedia::setVcrOp(int op) +{ + vcrMutex.lock(); + vcrFlag = op; + vcrMutex.unlock(); + + if (playVideo) + playVideo->setVcrOp(op); + + if (playAudio) + playAudio->setVcrOp(op); +} + +void DemuxMedia::startDemuxing() +{ + MediaPacket *mediaPkt; + int is_video_frame; + int rv; + + if ((audioQueue == NULL) || (videoQueue == NULL)) + return; + + while (1) + { + vcrMutex.lock(); + switch (vcrFlag) + { + case VCR_PLAY: + vcrFlag = 0; + vcrMutex.unlock(); + continue; + break; + + case VCR_PAUSE: + vcrMutex.unlock(); + usleep(1000 * 100); + continue; + break; + + case VCR_STOP: + vcrMutex.unlock(); + usleep(1000 * 100); + continue; + break; + + default: + vcrMutex.unlock(); + break; + } + + if ((audioQueue->count() >= 20) || (videoQueue->count() >= 20)) + { + if (!threadsStarted) + startAudioVideoThreads(); + + usleep(1000 * 20); + } + + mediaPkt = new MediaPacket; + rv = xrdpvr_get_frame(&mediaPkt->av_pkt, + &is_video_frame, + &mediaPkt->delay_in_us); + if (rv < 0) + { + /* looks like we reached end of file */ + delete mediaPkt; + usleep(1000 * 100); + continue; + } + + if (is_video_frame) + videoQueue->enqueue(mediaPkt); + else + audioQueue->enqueue(mediaPkt); + + } /* end while (1) */ +} + +PlayVideo * DemuxMedia::getPlayVideoInstance() +{ + return this->playVideo; +} + +void DemuxMedia::startAudioVideoThreads() +{ + if (threadsStarted) + return; + + playVideoThread->start(); + playAudioThread->start(); + threadsStarted = true; +} diff --git a/vrplayer/demuxmedia.h b/vrplayer/demuxmedia.h new file mode 100644 index 00000000..ecb25462 --- /dev/null +++ b/vrplayer/demuxmedia.h @@ -0,0 +1,65 @@ +#ifndef DEMUXMEDIA_H +#define DEMUXMEDIA_H + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include +#endif + +#include +#include +#include +#include +#include + +#include "mediapacket.h" +#include "playaudio.h" +#include "playvideo.h" + +/* ffmpeg related stuff */ +extern "C" +{ + #include + #include +} + +#define VCR_PLAY 1 +#define VCR_PAUSE 2 +#define VCR_STOP 3 +#define VCR_REWIND 4 +#define VCR_POWER_OFF 5 + +class DemuxMedia : public QObject +{ + Q_OBJECT +public: + explicit DemuxMedia(QObject *parent = 0, QQueue *audioQueue = 0, + QQueue *videoQueue = 0, void *channel = 0, int stream_id = 101); + + void setVcrOp(int op); + +public slots: + void startDemuxing(); + PlayVideo *getPlayVideoInstance(); + +private: + QQueue *audioQueue; + QQueue *videoQueue; + QMutex vcrMutex; + int vcrFlag; + void *channel; + PlayVideo *playVideo; + QThread *playVideoThread; + PlayAudio *playAudio; + QThread *playAudioThread; + int stream_id; + bool threadsStarted; + QMutex sendMutex; + + void startAudioVideoThreads(); +}; + +#endif // DEMUXMEDIA_H diff --git a/vrplayer/mainwindow.cpp b/vrplayer/mainwindow.cpp index 7f41eee9..f43d44b3 100644 --- a/vrplayer/mainwindow.cpp +++ b/vrplayer/mainwindow.cpp @@ -10,70 +10,56 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + /* connect to remote client */ + interface = new OurInterface(); + if (interface->oneTimeInit()) + { + oneTimeInitSuccess = false; + + /* connection to remote client failed; error msg has */ + /* already been displayed so it's ok to close app now */ + QTimer::singleShot(1000, qApp, SLOT(quit())); + } + + oneTimeInitSuccess = true; + remoteClientInited = false; ui->setupUi(this); acceptSliderMove = false; - decoderThread = new DecoderThread(); setupUI(); + vcrFlag = 0; -/* 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(this, SIGNAL(onGeometryChanged(int,int,int,int)), + interface, SLOT(onGeometryChanged(int,int,int,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))); + connect(interface, SIGNAL(onMediaDurationInSeconds(int)), + this, SLOT(onMediaDurationInSeconds(int))); } MainWindow::~MainWindow() { - delete ui; + if (oneTimeInitSuccess) + 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) +void MainWindow::resizeEvent(QResizeEvent *) { QRect rect; getVdoGeometry(&rect); - emit on_geometryChanged(rect.x(), rect.y(), rect.width(), rect.height()); + interface->sendGeometry(rect); } -void MainWindow::moveEvent(QMoveEvent *e) +void MainWindow::moveEvent(QMoveEvent *) { QRect rect; getVdoGeometry(&rect); - emit on_geometryChanged(rect.x(), rect.y(), rect.width(), rect.height()); + interface->sendGeometry(rect); } void MainWindow::setupUI() @@ -100,8 +86,8 @@ void MainWindow::setupUI() 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))); + connect(slider, SIGNAL(actionTriggered(int)), this, SLOT(onSliderActionTriggered(int))); + connect(slider, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int))); /* setup label to display media duration */ lblDuration = new QLabel("00:00:00"); @@ -115,19 +101,23 @@ void MainWindow::setupUI() hboxLayoutMiddle->addWidget(lblDuration); /* setup play button */ - btnPlay = new QPushButton("P"); + btnPlay = new QPushButton("Play"); btnPlay->setMinimumHeight(40); btnPlay->setMaximumHeight(40); btnPlay->setMinimumWidth(40); btnPlay->setMaximumWidth(40); - connect(btnPlay, SIGNAL(clicked(bool)), this, SLOT(on_btnPlayClicked(bool))); + btnPlay->setCheckable(true); + connect(btnPlay, SIGNAL(clicked(bool)), + this, SLOT(onBtnPlayClicked(bool))); /* setup stop button */ - btnStop = new QPushButton("S"); + btnStop = new QPushButton("Stop"); btnStop->setMinimumHeight(40); btnStop->setMaximumHeight(40); btnStop->setMinimumWidth(40); btnStop->setMaximumWidth(40); + connect(btnStop, SIGNAL(clicked(bool)), + this, SLOT(onBtnStopClicked(bool))); /* setup rewind button */ btnRewind = new QPushButton("R"); @@ -135,12 +125,14 @@ void MainWindow::setupUI() btnRewind->setMaximumHeight(40); btnRewind->setMinimumWidth(40); btnRewind->setMaximumWidth(40); + connect(btnRewind, SIGNAL(clicked(bool)), + this, SLOT(onBtnRewindClicked(bool))); /* add buttons to bottom panel */ hboxLayoutBottom = new QHBoxLayout; hboxLayoutBottom->addWidget(btnPlay); hboxLayoutBottom->addWidget(btnStop); - hboxLayoutBottom->addWidget(btnRewind); + //hboxLayoutBottom->addWidget(btnRewind); hboxLayoutBottom->addStretch(); /* add all three layouts to one vertical layout */ @@ -171,7 +163,7 @@ void MainWindow::openMediaFile() filename = QFileDialog::getOpenFileName(this, "Select Media File", filename); } - decoderThread->setFilename(filename); + interface->setFilename(filename); } void MainWindow::getVdoGeometry(QRect *rect) @@ -187,48 +179,120 @@ void MainWindow::getVdoGeometry(QRect *rect) rect->setHeight(lblVideo->geometry().height()); } +void MainWindow::clearDisplay() +{ + QPixmap pixmap(100,100); + pixmap.fill(QColor(0x00, 0x00, 0x00)); + QPainter painter(&pixmap); + painter.setBrush(QBrush(Qt::black)); + lblVideo->setPixmap(pixmap); +} + /******************************************************************************* * actions and slots go here * ******************************************************************************/ void MainWindow::on_actionOpen_Media_File_triggered() { + if (vcrFlag != 0) + onBtnStopClicked(false); + openMediaFile(); + if (filename.length() == 0) + { + /* cancel btn was clicked */ + return; + } + + if (remoteClientInited) + { + remoteClientInited = false; + interface->deInitRemoteClient(); + interface->initRemoteClient(); + } + else + { + interface->initRemoteClient(); + } + + playVideo = interface->getPlayVideoInstance(); + if (playVideo) + { + connect(playVideo, SIGNAL(onElapsedtime(int)), + this, SLOT(onElapsedTime(int))); + } + + remoteClientInited = true; + interface->playMedia(); + + if (vcrFlag != 0) + { + interface->setVcrOp(VCR_PLAY); + btnPlay->setText("Pause"); + vcrFlag = VCR_PLAY; + } } void MainWindow::on_actionExit_triggered() { - /* TODO: confirm app exit */ + clearDisplay(); this->close(); } -void MainWindow::on_actionPlay_Media_triggered() +void MainWindow::onBtnPlayClicked(bool) { - // LK_TODO do we need this? if yes, should be same as on_btnPlayClicked() -#if 1 - decoderThread->start(); -#else - if (!decoder) - return; + if (vcrFlag == 0) + { + /* first time play button has been clicked */ + on_actionOpen_Media_File_triggered(); + btnPlay->setText("Pause"); + vcrFlag = VCR_PLAY; + } + else if (vcrFlag == VCR_PLAY) + { + /* btn clicked while in play mode - enter pause mode */ + btnPlay->setText("Play"); + interface->setVcrOp(VCR_PAUSE); + vcrFlag = VCR_PAUSE; + } + else if (vcrFlag == VCR_PAUSE) + { + /* btn clicked while in pause mode - enter play mode */ + btnPlay->setText("Pause"); + interface->setVcrOp(VCR_PLAY); + vcrFlag = VCR_PLAY; + } - decoder->init(filename); -#endif + else if (vcrFlag == VCR_STOP) + { + /* btn clicked while stopped - enter play mode */ + btnPlay->setText("Play"); + interface->setVcrOp(VCR_PLAY); + vcrFlag = VCR_PLAY; + } } -void MainWindow::on_decoderError(QString title, QString msg) +void MainWindow::onBtnRewindClicked(bool) { - QMessageBox::information(this, title, msg); + if (playVideo) + playVideo->onMediaSeek(0); } -void MainWindow::on_btnPlayClicked(bool flag) +void MainWindow::onBtnStopClicked(bool) { - if (filename.length() == 0) - openMediaFile(); + vcrFlag = VCR_STOP; + btnPlay->setText("Play"); + interface->setVcrOp(VCR_STOP); + + /* reset slider */ + slider->setSliderPosition(0); + lblCurrentPos->setText("00:00:00"); - decoderThread->start(); + /* clear screen by filling it with black */ + clearDisplay(); } -void MainWindow::on_mediaDurationInSeconds(int duration) +void MainWindow::onMediaDurationInSeconds(int duration) { int hours = 0; int minutes = 0; @@ -239,6 +303,9 @@ void MainWindow::on_mediaDurationInSeconds(int duration) slider->setMinimum(0); slider->setMaximum(duration * 100); /* in hundredth of a sec */ slider->setValue(0); + slider->setSliderPosition(0); + lblCurrentPos->setText("00:00:00"); + qDebug() << "media_duration=" << duration << " in hundredth of a sec:" << duration * 100; /* convert from seconds to hours:minutes:seconds */ hours = duration / 3600; @@ -258,7 +325,7 @@ void MainWindow::on_mediaDurationInSeconds(int duration) /** * time elapsed in 1/100th sec units since play started ******************************************************************************/ -void MainWindow::on_elapsedTime(int val) +void MainWindow::onElapsedTime(int val) { int hours = 0; int minutes = 0; @@ -266,9 +333,18 @@ void MainWindow::on_elapsedTime(int val) int duration = val / 100; char buf[20]; + if (vcrFlag == VCR_STOP) + { + qDebug() << "onElapsedTime: not updating slider coz of VCR_STOP"; + return; + } + /* if slider bar is down, do not update */ if (slider->isSliderDown()) + { + qDebug() << "onElapsedTime: not updating slider coz slider is down"; return; + } /* update progress bar */ slider->setSliderPosition(val); @@ -289,16 +365,17 @@ void MainWindow::on_elapsedTime(int val) lblCurrentPos->setText(QString(buf)); } -void MainWindow::on_sliderValueChanged(int value) +void MainWindow::onSliderValueChanged(int value) { if (acceptSliderMove) { acceptSliderMove = false; - emit on_mediaSeek(value / 100); + if (playVideo) + playVideo->onMediaSeek(value / 100); } } -void MainWindow::on_sliderActionTriggered(int action) +void MainWindow::onSliderActionTriggered(int action) { switch (action) { @@ -319,7 +396,7 @@ void MainWindow::on_sliderActionTriggered(int action) #if 1 // LK_TODO delete this -void MainWindow::mouseMoveEvent(QMouseEvent *e) +void MainWindow::mouseMoveEvent(QMouseEvent *) { //qDebug() << "mouseMoveEvent: x=" << e->globalX() << "y=" << e->globalY(); } diff --git a/vrplayer/mainwindow.h b/vrplayer/mainwindow.h index ed392ea2..ceddb683 100644 --- a/vrplayer/mainwindow.h +++ b/vrplayer/mainwindow.h @@ -1,6 +1,14 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include +#endif + #include #include #include @@ -14,12 +22,32 @@ #include #include #include +#include +#include +#include +#include "xrdpapi.h" +#include "xrdpvr.h" #include "decoder.h" -#include "decoderthread.h" +#include "ourinterface.h" +#include "playvideo.h" + +/* ffmpeg related stuff */ +extern "C" +{ + #include + #include +} -namespace Ui { -class MainWindow; +#define VCR_PLAY 1 +#define VCR_PAUSE 2 +#define VCR_STOP 3 +#define VCR_REWIND 4 +#define VCR_POWER_OFF 5 + +namespace Ui +{ + class MainWindow; } class MainWindow : public QMainWindow @@ -30,20 +58,23 @@ public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); +signals: + void onGeometryChanged(int x, int y, int widht, int height); + +public slots: + void onSliderValueChanged(int value); + 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); + void onBtnPlayClicked(bool flag); + void onBtnRewindClicked(bool flag); + void onBtnStopClicked(bool flag); + + void onMediaDurationInSeconds(int duration); + void onElapsedTime(int secs); + void onSliderActionTriggered(int value); protected: void resizeEvent(QResizeEvent *e); @@ -54,10 +85,6 @@ protected: private: Ui::MainWindow *ui; - QString filename; - Decoder *decoder; - DecoderThread *decoderThread; - /* for UI */ QLabel *lblCurrentPos; QLabel *lblDuration; @@ -73,10 +100,22 @@ private: QWidget *window; bool acceptSliderMove; + /* private stuff */ + OurInterface *interface; + PlayVideo *playVideo; + QString filename; + bool oneTimeInitSuccess; + bool remoteClientInited; + void *channel; + int stream_id; + int64_t elapsedTime; /* elapsed time in usecs since play started */ + int vcrFlag; + /* private methods */ void setupUI(); void openMediaFile(); void getVdoGeometry(QRect *rect); + void clearDisplay(); }; #endif // MAINWINDOW_H diff --git a/vrplayer/mainwindow.ui b/vrplayer/mainwindow.ui index 7d4a81c6..af924894 100644 --- a/vrplayer/mainwindow.ui +++ b/vrplayer/mainwindow.ui @@ -28,7 +28,6 @@ File - @@ -56,11 +55,6 @@ Exit application - - - Play Media - - diff --git a/vrplayer/mediapacket.cpp b/vrplayer/mediapacket.cpp new file mode 100644 index 00000000..c9c2b128 --- /dev/null +++ b/vrplayer/mediapacket.cpp @@ -0,0 +1,5 @@ +#include "mediapacket.h" + +MediaPacket::MediaPacket() +{ +} diff --git a/vrplayer/mediapacket.h b/vrplayer/mediapacket.h new file mode 100644 index 00000000..98dfce9f --- /dev/null +++ b/vrplayer/mediapacket.h @@ -0,0 +1,14 @@ +#ifndef MEDIAPACKET_H +#define MEDIAPACKET_H + +class MediaPacket +{ +public: + MediaPacket(); + + void *av_pkt; + int delay_in_us; + int seq; +}; + +#endif // MEDIAPACKET_H diff --git a/vrplayer/ourinterface.cpp b/vrplayer/ourinterface.cpp new file mode 100644 index 00000000..fb4ad3dc --- /dev/null +++ b/vrplayer/ourinterface.cpp @@ -0,0 +1,219 @@ +#include "ourinterface.h" + +OurInterface::OurInterface(QObject *parent) : + QObject(parent) +{ + channel = NULL; + savedGeometry.setX(0); + savedGeometry.setY(0); + savedGeometry.setWidth(0); + savedGeometry.setHeight(0); + stream_id = 101; + demuxMedia = 0; + //elapsedTime = 0; +} + +int OurInterface::oneTimeInit() +{ + /* connect to remote client */ + if (openVirtualChannel()) + return -1; + + /* register all formats/codecs */ + av_register_all(); + + return 0; +} + +void OurInterface::oneTimeDeinit() +{ + /* clean up resources */ + closeVirtualChannel(); +} + +void OurInterface::initRemoteClient() +{ + int64_t start_time; + int64_t duration; + + //elapsedTime = 0; + + if (sendMetadataFile()) + return; + + if (sendVideoFormat()) + return; + + if (sendAudioFormat()) + return; + + if (sendGeometry(savedGeometry)) + return; + + xrdpvr_play_media(channel, 101, filename.toAscii().data()); + + xrdpvr_get_media_duration(&start_time, &duration); + qDebug() << "ourInterface:initRemoteClient: emit onMediaDurationInSecs: dur=" << duration; + emit onMediaDurationInSeconds(duration); + + /* LK_TODO this needs to be undone in deinitRemoteClient() */ + if (!demuxMedia) + { + demuxMedia = new DemuxMedia(NULL, &audioQueue, &videoQueue, channel, stream_id); + demuxMediaThread = new QThread(this); + connect(demuxMediaThread, SIGNAL(started()), demuxMedia, SLOT(startDemuxing())); + demuxMedia->moveToThread(demuxMediaThread); + playVideo = demuxMedia->getPlayVideoInstance(); + } +} + +void OurInterface::deInitRemoteClient() +{ + /* perform clean up */ + xrdpvr_deinit_player(channel, 101); +} + +/** + * @brief Open a virtual connection to remote client + * + * @return 0 on success, -1 on failure + ******************************************************************************/ +int OurInterface::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_ErrorMsg("Connection failure", + "Error connecting to remote client. Application will close now"); + return -1; + } + return 0; +} + +int OurInterface::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 OurInterface::sendMetadataFile() +{ + if (xrdpvr_create_metadata_file(channel, filename.toAscii().data())) + { + emit on_ErrorMsg("I/O Error", + "An error occurred while sending data to remote client"); + return -1; + } + + return 0; +} + +int OurInterface::sendVideoFormat() +{ + if (xrdpvr_set_video_format(channel, stream_id)) + { + emit on_ErrorMsg("I/O Error", + "Error sending video format to remote client"); + return -1; + } + + return 0; +} + +int OurInterface::sendAudioFormat() +{ + if (xrdpvr_set_audio_format(channel, stream_id)) + { + emit on_ErrorMsg("I/O Error", + "Error sending audio format to remote client"); + return -1; + } + + return 0; +} + +int OurInterface::sendGeometry(QRect rect) +{ + int rv; + + savedGeometry.setX(rect.x()); + savedGeometry.setY(rect.y()); + savedGeometry.setWidth(rect.width()); + savedGeometry.setHeight(rect.height()); + + rv = xrdpvr_set_geometry(channel, stream_id, savedGeometry.x(), + savedGeometry.y(), savedGeometry.width(), + savedGeometry.height()); + + if (rv) + { + emit on_ErrorMsg("I/O Error", + "Error sending screen geometry to remote client"); + return -1; + } + + return 0; +} + +void OurInterface::onGeometryChanged(int x, int y, int width, int height) +{ + savedGeometry.setX(x); + savedGeometry.setY(y); + savedGeometry.setWidth(width); + savedGeometry.setHeight(height); + +#if 1 + qDebug() << "OurInterface:signal" << + "" << savedGeometry.x() << + "" << savedGeometry.y() << + "" << savedGeometry.width() << + "" << savedGeometry.height(); +#endif + + qDebug() << "setting geometry:channel=" << channel; + + if (channel) + { + xrdpvr_set_geometry(channel, 101, savedGeometry.x(), savedGeometry.y(), + savedGeometry.width(), savedGeometry.height()); + } +} + +void OurInterface::setFilename(QString filename) +{ + this->filename = filename; +} + +void OurInterface::playMedia() +{ + demuxMediaThread->start(); +} + +PlayVideo * OurInterface::getPlayVideoInstance() +{ + return this->playVideo; +} + +void OurInterface::setVcrOp(int op) +{ + if (demuxMedia) + demuxMedia->setVcrOp(op); +} diff --git a/vrplayer/ourinterface.h b/vrplayer/ourinterface.h new file mode 100644 index 00000000..ea402048 --- /dev/null +++ b/vrplayer/ourinterface.h @@ -0,0 +1,75 @@ +#ifndef OURINTERFACE_H +#define OURINTERFACE_H + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include +#endif + +#include +#include +#include // LK_TODO + +#include "xrdpvr.h" +#include "xrdpapi.h" +#include "demuxmedia.h" +#include "playvideo.h" + +/* ffmpeg related stuff */ +extern "C" +{ + #include + #include +} + +class OurInterface : public QObject +{ + Q_OBJECT + +public: + explicit OurInterface(QObject *parent = 0); + + /* public methods */ + int oneTimeInit(); + void oneTimeDeinit(); + void initRemoteClient(); + void deInitRemoteClient(); + int sendGeometry(QRect rect); + void setFilename(QString filename); + void playMedia(); + PlayVideo *getPlayVideoInstance(); + void setVcrOp(int op); + +public slots: + void onGeometryChanged(int x, int y, int width, int height); + +signals: + void on_ErrorMsg(QString title, QString msg); + void onMediaDurationInSeconds(int duration); + +private: + + /* private stuff */ + QQueue audioQueue; + QQueue videoQueue; + + DemuxMedia *demuxMedia; + QThread *demuxMediaThread; + PlayVideo *playVideo; + QString filename; + void *channel; + int stream_id; + QRect savedGeometry; + + /* private methods */ + int openVirtualChannel(); + int closeVirtualChannel(); + int sendMetadataFile(); + int sendVideoFormat(); + int sendAudioFormat(); +}; + +#endif // INTERFACE_H diff --git a/vrplayer/playaudio.cpp b/vrplayer/playaudio.cpp new file mode 100644 index 00000000..0e4ad4ae --- /dev/null +++ b/vrplayer/playaudio.cpp @@ -0,0 +1,87 @@ +#include "playaudio.h" +#include + +PlayAudio::PlayAudio(QObject *parent, + QQueue *audioQueue, + QMutex *sendMutex, + void *channel, + int stream_id) : + QObject(parent) +{ + this->audioQueue = audioQueue; + this->sendMutex = sendMutex; + this->channel = channel; + this->stream_id = stream_id; + this->vcrFlag = 0; +} + +void PlayAudio::play() +{ + MediaPacket *pkt; + + while (1) + { + vcrMutex.lock(); + switch (vcrFlag) + { + case VCR_PLAY: + vcrFlag = 0; + vcrMutex.unlock(); + continue; + break; + + case VCR_PAUSE: + vcrMutex.unlock(); + usleep(1000 * 100); + continue; + break; + + case VCR_STOP: + vcrMutex.unlock(); + clearAudioQ(); + usleep(1000 * 100); + continue; + break; + + default: + vcrMutex.unlock(); + goto label1; + break; + } + +label1: + + if (audioQueue->isEmpty()) + { + qDebug() << "PlayAudio::play: GOT EMPTY"; + usleep(1000 * 100); + continue; + } + + pkt = audioQueue->dequeue(); + sendMutex->lock(); + send_audio_pkt(channel, stream_id, pkt->av_pkt); + sendMutex->unlock(); + delete pkt; + usleep(pkt->delay_in_us); + } +} + +void PlayAudio::setVcrOp(int op) +{ + vcrMutex.lock(); + this->vcrFlag = op; + vcrMutex.unlock(); +} + +void PlayAudio::clearAudioQ() +{ + MediaPacket *pkt; + + while (!audioQueue->isEmpty()) + { + pkt = audioQueue->dequeue(); + av_free_packet((AVPacket *) pkt->av_pkt); + delete pkt; + } +} diff --git a/vrplayer/playaudio.h b/vrplayer/playaudio.h new file mode 100644 index 00000000..c50b435d --- /dev/null +++ b/vrplayer/playaudio.h @@ -0,0 +1,59 @@ +#ifndef PLAYAUDIO_H +#define PLAYAUDIO_H + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include +#endif + +#include +#include +#include + +#include "mediapacket.h" +#include "xrdpvr.h" + +/* ffmpeg related stuff */ +extern "C" +{ + #include + #include +} + +#define VCR_PLAY 1 +#define VCR_PAUSE 2 +#define VCR_STOP 3 +#define VCR_REWIND 4 +#define VCR_POWER_OFF 5 + +class PlayAudio : public QObject +{ + Q_OBJECT + +public: + explicit PlayAudio(QObject *parent = 0, + QQueue *audioQueue = 0, + QMutex *sendMutex = 0, + void *channel = 0, + int stream_id = 101); + + void setVcrOp(int op); + +public slots: + void play(); + +private: + QQueue *audioQueue; + QMutex *sendMutex; + QMutex vcrMutex; + int vcrFlag; + void *channel; + int stream_id; + + void clearAudioQ(); +}; + +#endif // PLAYAUDIO_H diff --git a/vrplayer/playvideo.cpp b/vrplayer/playvideo.cpp new file mode 100644 index 00000000..afb70c71 --- /dev/null +++ b/vrplayer/playvideo.cpp @@ -0,0 +1,173 @@ +#include "playvideo.h" +#include + +PlayVideo::PlayVideo(QObject *parent, + QQueue *videoQueue, + QMutex *sendMutex, + void *channel, + int stream_id) : + QObject(parent) +{ + this->videoQueue = videoQueue; + this->channel = channel; + this->sendMutex = sendMutex; + this->stream_id = stream_id; + elapsedTime = 0; + pausedTime = 0; + la_seekPos = -1; + vcrFlag = 0; + isStopped = false; +} + +void PlayVideo::play() +{ + MediaPacket *pkt; + + while (1) + { + vcrMutex.lock(); + switch (vcrFlag) + { + case VCR_PLAY: + vcrFlag = 0; + vcrMutex.unlock(); + if (pausedTime) + { + elapsedTime = av_gettime() - pausedTime; + pausedTime = 0; + } + isStopped = false; + continue; + break; + + case VCR_PAUSE: + vcrMutex.unlock(); + if (!pausedTime) + { + /* save amount of video played so far */ + pausedTime = av_gettime() - elapsedTime; + } + usleep(1000 * 100); + isStopped = false; + continue; + break; + + case VCR_STOP: + vcrMutex.unlock(); + if (isStopped) + { + usleep(1000 * 100); + continue; + } + clearVideoQ(); + elapsedTime = 0; + pausedTime = 0; + la_seekPos = -1; + xrdpvr_seek_media(0, 0); + isStopped = true; + continue; + break; + + default: + vcrMutex.unlock(); + goto label1; + break; + } + +label1: + + if (videoQueue->isEmpty()) + { + qDebug() << "PlayVideo::play: GOT EMPTY"; + usleep(1000 * 100); + continue; + } + + pkt = videoQueue->dequeue(); + sendMutex->lock(); + send_video_pkt(channel, stream_id, pkt->av_pkt); + sendMutex->unlock(); + delete pkt; + usleep(pkt->delay_in_us); + + updateMediaPos(); + + if (elapsedTime == 0) + elapsedTime = av_gettime(); + + /* time elapsed in 1/100th sec units since play started */ + emit onElapsedtime((av_gettime() - elapsedTime) / 10000); + } +} + +void PlayVideo::onMediaSeek(int value) +{ + posMutex.lock(); + la_seekPos = value; + posMutex.unlock(); +} + +void PlayVideo::updateMediaPos() +{ +#if 0 + if (elapsedTime == 0) + elapsedTime = av_gettime(); + + /* time elapsed in 1/100th sec units since play started */ + emit onElapsedtime((av_gettime() - elapsedTime) / 10000); +#endif + + posMutex.lock(); + if (la_seekPos >= 0) + { + qDebug() << "seeking to" << la_seekPos; + xrdpvr_seek_media(la_seekPos, 0); + elapsedTime = av_gettime() - la_seekPos * 1000000; + la_seekPos = -1; + } + posMutex.unlock(); +} + +void PlayVideo::setVcrOp(int op) +{ + vcrMutex.lock(); + this->vcrFlag = op; + vcrMutex.unlock(); +} + +void PlayVideo::clearVideoQ() +{ + MediaPacket *pkt; + + while (!videoQueue->isEmpty()) + { + pkt = videoQueue->dequeue(); + av_free_packet((AVPacket *) pkt->av_pkt); + delete pkt; + } +} + +#if 0 +void DecoderThread::updateSlider() +{ + if (elapsedTime == 0) + elapsedTime = av_gettime(); + + /* time elapsed in 1/100th sec units since play started */ + emit onElapsedtime((av_gettime() - elapsedTime) / 10000); + + mutex.lock(); + if (la_seekPos >= 0) + { + qDebug() << "seeking to" << la_seekPos; + //audioTimer->stop(); + //videoTimer->stop(); + xrdpvr_seek_media(la_seekPos, 0); + elapsedTime = av_gettime() - la_seekPos * 1000000; + //audioTimer->start(10); + //videoTimer->start(10); + la_seekPos = -1; + } + mutex.unlock(); +} +#endif diff --git a/vrplayer/playvideo.h b/vrplayer/playvideo.h new file mode 100644 index 00000000..e5f68b5e --- /dev/null +++ b/vrplayer/playvideo.h @@ -0,0 +1,71 @@ +#ifndef PLAYVIDEO_H +#define PLAYVIDEO_H + +#ifdef __cplusplus +#define __STDC_CONSTANT_MACROS +#ifdef _STDINT_H +#undef _STDINT_H +#endif +#include +#endif + +#include +#include +#include + +#include "mediapacket.h" +#include "xrdpvr.h" +#include "xrdpapi.h" + +/* ffmpeg related stuff */ +extern "C" +{ + #include + #include +} + +#define VCR_PLAY 1 +#define VCR_PAUSE 2 +#define VCR_STOP 3 +#define VCR_REWIND 4 +#define VCR_POWER_OFF 5 + +class PlayVideo : public QObject +{ + Q_OBJECT + +public: + explicit PlayVideo(QObject *parent = 0, + QQueue *videoQueue = 0, + QMutex *sendMutex = 0, + void *channel = 0, + int stream_id = 101); + + void onMediaSeek(int value); + void setVcrOp(int op); + +public slots: + void play(); + +signals: + void onElapsedtime(int val); /* in hundredth of a sec */ + +private: + QQueue *videoQueue; + + int vcrFlag; + QMutex vcrMutex; + QMutex *sendMutex; + QMutex posMutex; + int64_t la_seekPos; /* locked access; must hold posMutex */ + void *channel; + int stream_id; + int64_t elapsedTime; /* elapsed time in usecs since play started */ + int64_t pausedTime; /* time at which stream was paused */ + bool isStopped; + + void updateMediaPos(); + void clearVideoQ(); +}; + +#endif // PLAYVIDEO_H diff --git a/vrplayer/vrplayer.pro b/vrplayer/vrplayer.pro index 9aac8aff..1d035382 100644 --- a/vrplayer/vrplayer.pro +++ b/vrplayer/vrplayer.pro @@ -11,13 +11,19 @@ TEMPLATE = app SOURCES += main.cpp\ - mainwindow.cpp \ - decoder.cpp \ - decoderthread.cpp + playvideo.cpp \ + mainwindow.cpp \ + mediapacket.cpp \ + playaudio.cpp \ + demuxmedia.cpp \ + ourinterface.cpp HEADERS += mainwindow.h \ - decoder.h \ - decoderthread.h + mediapacket.h \ + playvideo.h \ + playaudio.h \ + demuxmedia.h \ + ourinterface.h FORMS += mainwindow.ui diff --git a/xrdpvr/xrdpvr.c b/xrdpvr/xrdpvr.c index 66dfea5a..b30869a8 100644 --- a/xrdpvr/xrdpvr.c +++ b/xrdpvr/xrdpvr.c @@ -133,6 +133,10 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename) { int i; + printf("$$$$$$ xrdpvr_play_media: setting audioTimeout & videoTimeout to -1\n"); + g_psi.videoTimeout = -1; + g_psi.audioTimeout = -1; + /* register all available fileformats and codecs */ av_register_all(); @@ -221,11 +225,106 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename) return 0; } -int xrdpvr_play_frame(void *channel, int stream_id) +static int firstAudioPkt = 1; +static int firstVideoPkt = 1; + +int xrdpvr_get_frame(void **av_pkt_ret, int *is_video_frame, int *delay_in_us) { - AVPacket av_pkt; + AVPacket *av_pkt; + double dts; + + /* alloc an AVPacket */ + if ((av_pkt = (AVPacket *) malloc(sizeof(AVPacket))) == NULL) + return -1; + + /* read one frame into AVPacket */ + if (av_read_frame(g_psi.p_format_ctx, av_pkt) < 0) + { + free(av_pkt); + return -1; + } + + if (av_pkt->stream_index == g_audio_index) + { + /* got an audio packet */ + dts = av_pkt->dts; + dts *= av_q2d(g_psi.p_format_ctx->streams[g_audio_index]->time_base); + + if (g_psi.audioTimeout < 0) + { + *delay_in_us = 1000 * 5; + g_psi.audioTimeout = dts; + } + else + { + *delay_in_us = (int) ((dts - g_psi.audioTimeout) * 1000000); + g_psi.audioTimeout = dts; + } + *is_video_frame = 0; - printf("xrdpvr_play_frame: entered\n"); + if (firstAudioPkt) + { + firstAudioPkt = 0; + //printf("##### first audio: dts=%f delay_in_ms=%d\n", dts, *delay_in_us); + } + } + else if (av_pkt->stream_index == g_video_index) + { + dts = av_pkt->dts; + + //printf("$$$ video raw_dts=%f raw_pts=%f\n", (double) av_pkt->dts, (double) av_pkt->dts); + + dts *= av_q2d(g_psi.p_format_ctx->streams[g_video_index]->time_base); + + if (g_psi.videoTimeout < 0) + { + *delay_in_us = 1000 * 5; + g_psi.videoTimeout = dts; + //printf("$$$ negative: videoTimeout=%f\n", g_psi.videoTimeout); + } + else + { + //printf("$$$ positive: videoTimeout_b4 =%f\n", g_psi.videoTimeout); + *delay_in_us = (int) ((dts - g_psi.videoTimeout) * 1000000); + g_psi.videoTimeout = dts; + //printf("$$$ positive: videoTimeout_aft=%f\n", g_psi.videoTimeout); + } + *is_video_frame = 1; + + if (firstVideoPkt) + { + firstVideoPkt = 0; + //printf("$$$ first video: dts=%f delay_in_ms=%d\n", dts, *delay_in_us); + } + } + + *av_pkt_ret = av_pkt; + return 0; +} + +int send_audio_pkt(void *channel, int stream_id, void *pkt_p) +{ + AVPacket *av_pkt = (AVPacket *) pkt_p; + + xrdpvr_send_audio_data(channel, stream_id, av_pkt->size, av_pkt->data); + av_free_packet(av_pkt); + free(av_pkt); +} + +int send_video_pkt(void *channel, int stream_id, void *pkt_p) +{ + AVPacket *av_pkt = (AVPacket *) pkt_p; + + xrdpvr_send_video_data(channel, stream_id, av_pkt->size, av_pkt->data); + av_free_packet(av_pkt); + free(av_pkt); +} + +int xrdpvr_play_frame(void *channel, int stream_id, int *videoTimeout, int *audioTimeout) +{ + AVPacket av_pkt; + double dts; + int delay_in_us; if (av_read_frame(g_psi.p_format_ctx, &av_pkt) < 0) { @@ -236,12 +335,55 @@ int xrdpvr_play_frame(void *channel, int stream_id) if (av_pkt.stream_index == g_audio_index) { xrdpvr_send_audio_data(channel, stream_id, av_pkt.size, av_pkt.data); - usleep(1000 * 1); + + dts = av_pkt.dts; + dts *= av_q2d(g_psi.p_format_ctx->streams[g_audio_index]->time_base); + + *audioTimeout = (int) ((dts - g_psi.audioTimeout) * 1000000); + *videoTimeout = -1; + + if (g_psi.audioTimeout > dts) + { + g_psi.audioTimeout = dts; + delay_in_us = 1000 * 40; + } + else + { + delay_in_us = (int) ((dts - g_psi.audioTimeout) * 1000000); + g_psi.audioTimeout = dts; + } + + //printf("audio delay: %d\n", delay_in_us); + usleep(delay_in_us); + //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 + + dts = av_pkt.dts; + dts *= av_q2d(g_psi.p_format_ctx->streams[g_video_index]->time_base); + + //printf("xrdpvr_play_frame:video: saved=%f dts=%f\n", g_psi.videoTimeout, dts); + + *videoTimeout = (int) ((dts - g_psi.videoTimeout) * 1000000); + *audioTimeout = -1; + + if (g_psi.videoTimeout > dts) + { + g_psi.videoTimeout = dts; + delay_in_us = 1000 * 40; + //printf("xrdpvr_play_frame:video1: saved=%f dts=%f delay_in_us=%d\n", g_psi.videoTimeout, dts, delay_in_us); + } + else + { + delay_in_us = (int) ((dts - g_psi.videoTimeout) * 1000000); + g_psi.videoTimeout = dts; + //printf("xrdpvr_play_frame:video2: saved=%f dts=%f delay_in_us=%d\n", g_psi.videoTimeout, dts, delay_in_us); + } + + //printf("video delay: %d\n", delay_in_us); + usleep(delay_in_us); } av_free_packet(&av_pkt); @@ -256,6 +398,9 @@ xrdpvr_seek_media(int64_t pos, int backward) printf("xrdpvr_seek_media() entered\n"); + g_psi.audioTimeout = -1; + g_psi.videoTimeout = -1; + seek_flag = (backward) ? AVSEEK_FLAG_BACKWARD : 0; seek_target = av_rescale_q(pos * AV_TIME_BASE, diff --git a/xrdpvr/xrdpvr.h b/xrdpvr/xrdpvr.h index 4368db1f..49ec6c77 100644 --- a/xrdpvr/xrdpvr.h +++ b/xrdpvr/xrdpvr.h @@ -38,9 +38,12 @@ 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); +int xrdpvr_play_frame(void *channel, int stream_id, int *vdoTimeout, int *audioTimeout); void xrdpvr_get_media_duration(int64_t *start_time, int64_t *duration); int xrdpvr_seek_media(int64_t pos, int backward); +int xrdpvr_get_frame(void **av_pkt_ret, int *is_video_frame, int *delay_in_us); +int send_audio_pkt(void *channel, int stream_id, void *pkt_p); +int send_video_pkt(void *channel, int stream_id, void *pkt_p); #ifdef __cplusplus } diff --git a/xrdpvr/xrdpvr_internal.h b/xrdpvr/xrdpvr_internal.h index 995306c3..ca15caf1 100644 --- a/xrdpvr/xrdpvr_internal.h +++ b/xrdpvr/xrdpvr_internal.h @@ -187,6 +187,8 @@ typedef struct _player_state_info int audio_stream_index; int video_stream_index; + double audioTimeout; + double videoTimeout; /* LK_TODO delete this after we fix the problem */ AVFrame *frame;