vrplayer: work on client timing

ulab-next
Jay Sorg 11 years ago
parent f5e9bc3308
commit ad0532b1bf

@ -647,8 +647,9 @@ process_message_channel_data(struct stream *s)
{
rv = drdynvc_data_in(s, chan_id, chan_flags, length, total_length);
}
else if (chan_id == ((struct xrdp_api_data *)
(g_api_con_trans->callback_data))->chan_id)
else if ((g_api_con_trans != 0) &&
(chan_id == ((struct xrdp_api_data *)
(g_api_con_trans->callback_data))->chan_id))
{
LOG(10, ("process_message_channel_data length %d total_length %d "
"chan_flags 0x%8.8x", length, total_length, chan_flags));

@ -3,26 +3,25 @@
#include "demuxmedia.h"
DemuxMedia::DemuxMedia(QObject *parent, QQueue<MediaPacket *> *audioQueue,
QQueue<MediaPacket *> *videoQueue, void *channel, int stream_id) :
QObject(parent)
DemuxMedia::DemuxMedia(QObject *parent, QQueue<MediaPacket *> *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;
this->elapsedTime = 0;
this->la_seekPos = -1;
this->isStopped = 0;
this->pausedTime = 0;
this->videoQueue = videoQueue;
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);
playVideo = new PlayVideo(NULL, videoQueue, &sendMutex, channel, 101, 24);
playVideoThread = new QThread(this);
connect(playVideoThread, SIGNAL(started()), playVideo, SLOT(play()));
playVideo->moveToThread(playVideoThread);
playVideoThread->start();
}
void DemuxMedia::setVcrOp(int op)
@ -30,12 +29,6 @@ void DemuxMedia::setVcrOp(int op)
vcrMutex.lock();
vcrFlag = op;
vcrMutex.unlock();
if (playVideo)
playVideo->setVcrOp(op);
if (playAudio)
playAudio->setVcrOp(op);
}
void DemuxMedia::startDemuxing()
@ -44,9 +37,6 @@ void DemuxMedia::startDemuxing()
int is_video_frame;
int rv;
if ((audioQueue == NULL) || (videoQueue == NULL))
return;
while (1)
{
vcrMutex.lock();
@ -55,18 +45,40 @@ void DemuxMedia::startDemuxing()
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();
usleep(1000 * 100);
if (isStopped)
{
usleep(1000 * 100);
continue;
}
elapsedTime = 0;
pausedTime = 0;
la_seekPos = -1;
xrdpvr_seek_media(0, 0);
isStopped = true;
continue;
break;
@ -75,14 +87,6 @@ void DemuxMedia::startDemuxing()
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,
@ -91,31 +95,61 @@ void DemuxMedia::startDemuxing()
{
/* looks like we reached end of file */
delete mediaPkt;
playVideo->onMediaRestarted();
usleep(1000 * 100);
xrdpvr_seek_media(0, 0);
this->elapsedTime = 0;
continue;
}
if (is_video_frame)
{
sendMutex.lock();
#if 1
videoQueue->enqueue(mediaPkt);
#else
send_video_pkt(channel, stream_id, mediaPkt->av_pkt);
delete mediaPkt;
#endif
sendMutex.unlock();
}
else
audioQueue->enqueue(mediaPkt);
{
int frame;
sendMutex.lock();
send_audio_pkt(channel, stream_id, mediaPkt->av_pkt);
sendMutex.unlock();
xrdpvr_read_ack(channel, &frame);
delete mediaPkt;
}
updateMediaPos();
if (elapsedTime == 0)
{
elapsedTime = av_gettime();
}
/* time elapsed in 1/100th sec units since play started */
emit onElapsedtime((av_gettime() - elapsedTime) / 10000);
} /* end while (1) */
}
PlayVideo * DemuxMedia::getPlayVideoInstance()
void DemuxMedia::onMediaSeek(int value)
{
return this->playVideo;
posMutex.lock();
la_seekPos = value;
posMutex.unlock();
}
void DemuxMedia::startAudioVideoThreads()
void DemuxMedia::updateMediaPos()
{
if (threadsStarted)
return;
playVideoThread->start();
playAudioThread->start();
threadsStarted = true;
posMutex.lock();
if (la_seekPos >= 0)
{
xrdpvr_seek_media(la_seekPos, 0);
elapsedTime = av_gettime() - la_seekPos * 1000000;
la_seekPos = -1;
}
posMutex.unlock();
}

@ -37,33 +37,39 @@ class DemuxMedia : public QObject
Q_OBJECT
public:
explicit DemuxMedia(QObject *parent = 0, QQueue<MediaPacket *> *audioQueue = 0,
QQueue<MediaPacket *> *videoQueue = 0, void *channel = 0, int stream_id = 101);
explicit DemuxMedia(QObject *parent = 0, QQueue<MediaPacket *> *videoQueue = 0,
void *channel = 0, int stream_id = 101);
void setVcrOp(int op);
public slots:
void startDemuxing();
PlayVideo *getPlayVideoInstance();
void onMediaSeek(int value);
private:
QQueue<MediaPacket *> *audioQueue;
QMutex vcrMutex;
int vcrFlag;
void *channel;
int stream_id;
QMutex sendMutex;
QMutex posMutex;
int64_t elapsedTime; /* elapsed time in usecs since play started */
int64_t pausedTime; /* time at which stream was paused */
int64_t la_seekPos; /* locked access; must hold posMutex */
bool isStopped;
QQueue<MediaPacket *> *videoQueue;
QMutex vcrMutex;
int vcrFlag;
void *channel;
PlayVideo *playVideo;
QThread *playVideoThread;
PlayAudio *playAudio;
QThread *playAudioThread;
int stream_id;
bool threadsStarted;
QMutex sendMutex;
void startAudioVideoThreads();
void updateMediaPos();
signals:
void onMediaRestarted();
signals:
void onElapsedtime(int val); /* in hundredth of a sec */
};
#endif // DEMUXMEDIA_H

@ -1,3 +1,4 @@
#include <QtGui/QApplication>
#include "mainwindow.h"

@ -66,8 +66,8 @@ MainWindow::~MainWindow()
{
delete ui;
if (moveResizeTimer)
delete moveResizeTimer;
//if (moveResizeTimer)
// delete moveResizeTimer;
}
void MainWindow::closeEvent(QCloseEvent *event)
@ -86,50 +86,50 @@ void MainWindow::closeEvent(QCloseEvent *event)
void MainWindow::resizeEvent(QResizeEvent *)
{
if (vcrFlag != VCR_PLAY)
//if (vcrFlag != VCR_PLAY)
{
QRect rect;
getVdoGeometry(&rect);
interface->sendGeometry(rect);
return;
//return;
}
interface->setVcrOp(VCR_PAUSE);
vcrFlag = VCR_PAUSE;
if (!moveResizeTimer)
{
moveResizeTimer = new QTimer;
connect(moveResizeTimer, SIGNAL(timeout()),
this, SLOT(onMoveCompleted()));
}
lblVideo->setStyleSheet("QLabel { background-color : black; color : blue; }");
moveResizeTimer->start(1000);
//interface->setVcrOp(VCR_PAUSE);
//vcrFlag = VCR_PAUSE;
//if (!moveResizeTimer)
//{
// moveResizeTimer = new QTimer;
// connect(moveResizeTimer, SIGNAL(timeout()),
// this, SLOT(onMoveCompleted()));
//}
//lblVideo->setStyleSheet("QLabel { background-color : black; color : blue; }");
//moveResizeTimer->start(1000);
}
void MainWindow::moveEvent(QMoveEvent *)
{
if (vcrFlag != VCR_PLAY)
//if (vcrFlag != VCR_PLAY)
{
QRect rect;
getVdoGeometry(&rect);
interface->sendGeometry(rect);
return;
//return;
}
interface->setVcrOp(VCR_PAUSE);
vcrFlag = VCR_PAUSE;
if (!moveResizeTimer)
{
moveResizeTimer = new QTimer;
connect(moveResizeTimer, SIGNAL(timeout()),
this, SLOT(onMoveCompleted()));
}
lblVideo->setStyleSheet("QLabel { background-color : black; color : blue; }");
moveResizeTimer->start(1000);
//interface->setVcrOp(VCR_PAUSE);
//vcrFlag = VCR_PAUSE;
//if (!moveResizeTimer)
//{
// moveResizeTimer = new QTimer;
// connect(moveResizeTimer, SIGNAL(timeout()),
// this, SLOT(onMoveCompleted()));
//}
//lblVideo->setStyleSheet("QLabel { background-color : black; color : blue; }");
//moveResizeTimer->start(1000);
}
void MainWindow::onVolSliderValueChanged(int value)
@ -253,10 +253,17 @@ void MainWindow::openMediaFile()
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",
// QDir::currentPath());
filename = QFileDialog::getOpenFileName(this, "Select Media File",
QDir::currentPath());
QDir::currentPath(),
"Media *.mov *.mp4 *.mkv (*.mov *.mp4 *.mkv)");
}
else
{
@ -302,13 +309,19 @@ void MainWindow::clearDisplay()
void MainWindow::on_actionOpen_Media_File_triggered()
{
if (vcrFlag != 0)
{
onBtnStopClicked(true);
}
/* if media was specified on cmd line, use it just once */
if (gotMediaOnCmdline)
{
gotMediaOnCmdline = false;
}
else
{
openMediaFile();
}
if (filename.length() == 0)
{
@ -327,10 +340,10 @@ void MainWindow::on_actionOpen_Media_File_triggered()
interface->initRemoteClient();
}
playVideo = interface->getPlayVideoInstance();
if (playVideo)
demuxMedia = interface->getDemuxMediaInstance();
if (demuxMedia)
{
connect(playVideo, SIGNAL(onElapsedtime(int)),
connect(demuxMedia, SIGNAL(onElapsedtime(int)),
this, SLOT(onElapsedTime(int)));
}
@ -355,7 +368,7 @@ void MainWindow::onBtnPlayClicked(bool)
{
if (vcrFlag == 0)
{
/* first time play button has been clicked */
/* first time play button3 has been clicked */
on_actionOpen_Media_File_triggered();
btnPlay->setText("Pause");
vcrFlag = VCR_PLAY;
@ -385,8 +398,8 @@ void MainWindow::onBtnPlayClicked(bool)
void MainWindow::onBtnRewindClicked(bool)
{
if (playVideo)
playVideo->onMediaSeek(0);
//if (playVideo)
// playVideo->onMediaSeek(0);
}
void MainWindow::onBtnStopClicked(bool)
@ -401,6 +414,8 @@ void MainWindow::onBtnStopClicked(bool)
/* clear screen by filling it with black */
clearDisplay();
btnPlay->setChecked(false);
}
void MainWindow::onMediaDurationInSeconds(int duration)
@ -479,8 +494,10 @@ void MainWindow::onSliderValueChanged(int value)
if (acceptSliderMove)
{
acceptSliderMove = false;
if (playVideo)
playVideo->onMediaSeek(value / 100);
if (demuxMedia != NULL)
{
demuxMedia->onMediaSeek(value / 100);
}
}
}
@ -503,6 +520,7 @@ void MainWindow::onSliderActionTriggered(int action)
}
}
// not called
void MainWindow::onMoveCompleted()
{
QRect rect;
@ -512,7 +530,7 @@ void MainWindow::onMoveCompleted()
interface->setVcrOp(VCR_PLAY);
vcrFlag = VCR_PLAY;
moveResizeTimer->stop();
//moveResizeTimer->stop();
}
void MainWindow::on_actionAbout_triggered()

@ -111,7 +111,8 @@ private:
/* private stuff */
OurInterface *interface;
PlayVideo *playVideo;
//PlayVideo *playVideo;
DemuxMedia *demuxMedia;
QString filename;
bool oneTimeInitSuccess;
bool remoteClientInited;

@ -60,11 +60,11 @@ void OurInterface::initRemoteClient()
/* LK_TODO this needs to be undone in deinitRemoteClient() */
if (!demuxMedia)
{
demuxMedia = new DemuxMedia(NULL, &audioQueue, &videoQueue, channel, stream_id);
demuxMedia = new DemuxMedia(NULL, &videoQueue, channel, stream_id);
demuxMediaThread = new QThread(this);
connect(demuxMediaThread, SIGNAL(started()), demuxMedia, SLOT(startDemuxing()));
demuxMedia->moveToThread(demuxMediaThread);
playVideo = demuxMedia->getPlayVideoInstance();
//playVideo = demuxMedia->getPlayVideoInstance();
}
}
@ -216,11 +216,17 @@ void OurInterface::playMedia()
demuxMediaThread->start();
}
PlayVideo * OurInterface::getPlayVideoInstance()
//PlayVideo * OurInterface::getPlayVideoInstance()
//{
// return this->playVideo;
//}
DemuxMedia * OurInterface::getDemuxMediaInstance()
{
return this->playVideo;
return this->demuxMedia;
}
void OurInterface::setVcrOp(int op)
{
if (demuxMedia)

@ -40,7 +40,8 @@ public:
int sendGeometry(QRect rect);
void setFilename(QString filename);
void playMedia();
PlayVideo *getPlayVideoInstance();
//PlayVideo *getPlayVideoInstance();
DemuxMedia *getDemuxMediaInstance();
void setVcrOp(int op);
int setVolume(int volume);
@ -54,12 +55,12 @@ signals:
private:
/* private stuff */
QQueue<MediaPacket *> audioQueue;
QQueue<MediaPacket *> videoQueue;
DemuxMedia *demuxMedia;
QThread *demuxMediaThread;
PlayVideo *playVideo;
//PlayVideo *playVideo;
QString filename;
void *channel;
int stream_id;

@ -58,11 +58,11 @@ label1:
if (audioQueue->isEmpty())
{
qDebug() << "PlayAudio::play: GOT EMPTY";
usleep(1000 * 100);
usleep(1000 * 10);
continue;
}
printf("")
printf("");
pkt = audioQueue->dequeue();
sendMutex->lock();
send_audio_pkt(channel, stream_id, pkt->av_pkt);

@ -1,5 +1,6 @@
#include <unistd.h>
#include <sys/time.h>
#include "playvideo.h"
#include <QDebug>
@ -8,182 +9,58 @@ PlayVideo::PlayVideo(QObject *parent,
QQueue<MediaPacket *> *videoQueue,
QMutex *sendMutex,
void *channel,
int stream_id) :
int stream_id, int fps) :
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;
this->fps = fps;
}
/**
******************************************************************************/
static int
get_mstime(void)
{
struct timeval tp;
gettimeofday(&tp, 0);
return (tp.tv_sec * 1000) + (tp.tv_usec / 1000);
}
void PlayVideo::play()
{
MediaPacket *pkt;
int usl;
int now_time;
int sleep_time;
int last_display_time;
last_display_time = 0;
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:
printf("video\n");
sendMutex->lock();
if (videoQueue->isEmpty())
{
printf("---empty\n");
usleep(1000);
sendMutex->unlock();
usleep(10 * 1000);
continue;
}
pkt = videoQueue->dequeue();
sendMutex->lock();
send_video_pkt(channel, stream_id, pkt->av_pkt);
sendMutex->unlock();
usl = 1000; // pkt->delay_in_us;
if (usl < 0)
now_time = get_mstime();
sleep_time = now_time - last_display_time;
if (sleep_time > (1000 / fps))
{
usl = 0;
sleep_time = (1000 / fps);
}
if (usl > 100 * 1000)
if (sleep_time > 0)
{
usl = 100 * 1000;
usleep(sleep_time * 1000);
}
usleep(usl);
delete pkt;
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::onMediaRestarted()
{
elapsedTime = av_gettime();
}
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);
last_display_time = now_time;
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

@ -39,34 +39,36 @@ public:
QQueue<MediaPacket *> *videoQueue = 0,
QMutex *sendMutex = 0,
void *channel = 0,
int stream_id = 101);
int stream_id = 101,
int fps = 24);
void onMediaSeek(int value);
void setVcrOp(int op);
void onMediaRestarted();
//void onMediaSeek(int value);
//void setVcrOp(int op);
//void onMediaRestarted();
public slots:
void play();
signals:
void onElapsedtime(int val); /* in hundredth of a sec */
//signals:
// void onElapsedtime(int val); /* in hundredth of a sec */
private:
QQueue<MediaPacket *> *videoQueue;
int vcrFlag;
QMutex vcrMutex;
// int vcrFlag;
// QMutex vcrMutex;
QMutex *sendMutex;
QMutex posMutex;
int64_t la_seekPos; /* locked access; must hold posMutex */
// 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;
int fps;
// 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();
// void updateMediaPos();
// void clearVideoQ();
};
#endif // PLAYVIDEO_H

@ -222,7 +222,7 @@ xrdpvr_play_media(void *channel, int stream_id, char *filename)
return -1;
}
#if 0
#if 1
/* print media info to standard out */
av_dump_format(g_psi.p_format_ctx, 0, filename, 0);
#endif
@ -231,13 +231,15 @@ 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 == CODEC_TYPE_VIDEO &&
g_video_index < 0)
g_psi.p_format_ctx->streams[i]->codec->codec_id == CODEC_ID_H264 &&
g_video_index < 0)
{
g_video_index = i;
}
if (g_psi.p_format_ctx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO &&
g_audio_index < 0)
g_psi.p_format_ctx->streams[i]->codec->codec_id == CODEC_ID_AAC &&
g_audio_index < 0)
{
g_audio_index = i;
}
@ -468,7 +470,7 @@ xrdpvr_play_frame(void *channel, int stream_id, int *videoTimeout, int *audioTim
AVBitStreamFilterContext *bsfc;
AVPacket new_pkt;
printf("xrdpvr_play_frame:\n");
//printf("xrdpvr_play_frame:\n");
if (av_read_frame(g_psi.p_format_ctx, &av_pkt) < 0)
{
@ -726,7 +728,7 @@ xrdpvr_send_video_data(void *channel, uint32_t stream_id, uint32_t data_len, uin
int rv;
int len;
printf("xrdpvr_send_video_data:\n");
//printf("xrdpvr_send_video_data:\n");
stream_new(s, MAX_PDU_SIZE + data_len);
stream_ins_u32_le(s, 0); /* number of bytes to follow */
@ -771,7 +773,7 @@ xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len, uin
int rv;
int len;
printf("xrdpvr_send_audio_data:\n");
//printf("xrdpvr_send_audio_data:\n");
stream_new(s, MAX_PDU_SIZE + data_len);
stream_ins_u32_le(s, 0); /* number of bytes to follow */
@ -791,12 +793,6 @@ xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len, uin
rv = xrdpvr_write_to_client(channel, s);
stream_free(s);
// read ack back
stream_new(s, MAX_PDU_SIZE);
xrdpvr_read_from_client(channel, s, 4, 1000);
//hexdump(s->data, s->p - s->data);
stream_free(s);
return rv;
}
@ -1007,3 +1003,16 @@ xrdpvr_send_init(void *channel)
stream_free(s);
return rv;
}
int
xrdpvr_read_ack(void *channel, int *frame)
{
STREAM *s;
stream_new(s, MAX_PDU_SIZE);
xrdpvr_read_from_client(channel, s, 4, 1000);
s->p = s->data;
stream_ext_u32_le(s, *frame);
stream_free(s);
return 0;
}

@ -48,6 +48,7 @@ int send_audio_pkt(void *channel, int stream_id, void *pkt_p);
int send_video_pkt(void *channel, int stream_id, void *pkt_p);
int xrdpvr_set_volume(void *channel, int volume);
int xrdpvr_send_init(void *channel);
int xrdpvr_read_ack(void *channel, int *frame);
#ifdef __cplusplus
}

Loading…
Cancel
Save