1006 lines
27 KiB
C
1006 lines
27 KiB
C
/**
|
|
* xrdp: A Remote Desktop Protocol server.
|
|
*
|
|
* Copyright (C) Laxmikant Rashinkar 2012-2013 LK.Rashinkar@gmail.com
|
|
* Copyright (C) Jay Sorg 2013 jay.sorg@gmail.com
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* a program that uses xrdpapi and ffmpeg to redirect media streams
|
|
* to a FreeRDP client where it is decompressed and played locally
|
|
*
|
|
*/
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include <config_ac.h>
|
|
#endif
|
|
|
|
#include "xrdpvr.h"
|
|
#include "xrdpvr_internal.h"
|
|
|
|
/* globals */
|
|
PLAYER_STATE_INFO g_psi;
|
|
int g_video_index = -1;
|
|
int g_audio_index = -1;
|
|
|
|
/*****************************************************************************/
|
|
/* produce a hex dump */
|
|
void
|
|
hexdump(char *p, int len)
|
|
{
|
|
unsigned char *line;
|
|
int i;
|
|
int thisline;
|
|
int offset;
|
|
|
|
line = (unsigned char *)p;
|
|
offset = 0;
|
|
|
|
while (offset < len)
|
|
{
|
|
printf("%04x ", offset);
|
|
thisline = len - offset;
|
|
|
|
if (thisline > 16)
|
|
{
|
|
thisline = 16;
|
|
}
|
|
|
|
for (i = 0; i < thisline; i++)
|
|
{
|
|
printf("%02x ", line[i]);
|
|
}
|
|
|
|
for (; i < 16; i++)
|
|
{
|
|
printf(" ");
|
|
}
|
|
|
|
for (i = 0; i < thisline; i++)
|
|
{
|
|
printf("%c", (line[i] >= 0x20 && line[i] < 0x7f) ? line[i] : '.');
|
|
}
|
|
|
|
printf("\n");
|
|
offset += thisline;
|
|
line += thisline;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* initialize the media player
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
* @param filename media file to play
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_init_player(void *channel, int stream_id, char *filename)
|
|
{
|
|
printf("xrdpvr_init_player:\n");
|
|
if ((channel == NULL) || (stream_id <= 0) || (filename == NULL))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
xrdpvr_send_init(channel);
|
|
}
|
|
|
|
/**
|
|
* de-initialize the media player
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_deinit_player(void *channel, int stream_id)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
if ((channel == NULL) || (stream_id <= 0))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/* do local clean up */
|
|
if (g_psi.frame != 0)
|
|
{
|
|
av_free(g_psi.frame);
|
|
g_psi.frame = 0;
|
|
}
|
|
if (g_psi.p_audio_codec_ctx != 0)
|
|
{
|
|
avcodec_close(g_psi.p_audio_codec_ctx);
|
|
g_psi.p_audio_codec_ctx = 0;
|
|
}
|
|
if (g_psi.p_video_codec_ctx != 0)
|
|
{
|
|
avcodec_close(g_psi.p_video_codec_ctx);
|
|
g_psi.p_video_codec_ctx = 0;
|
|
}
|
|
//avformat_close_input(&g_psi.p_format_ctx);
|
|
if (g_psi.p_format_ctx != 0)
|
|
{
|
|
av_close_input_file(g_psi.p_format_ctx);
|
|
g_psi.p_format_ctx = 0;
|
|
}
|
|
|
|
/* do remote cleanup */
|
|
|
|
stream_new(s, MAX_PDU_SIZE);
|
|
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_DEINIT_XRDPVR);
|
|
stream_ins_u32_le(s, stream_id);
|
|
|
|
/* 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 0;
|
|
}
|
|
|
|
/**
|
|
* play the media
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
* @param filename media file to play
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
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();
|
|
|
|
/* open media file - this will read just the header */
|
|
//if (avformat_open_input(&g_psi.p_format_ctx, filename, NULL, NULL))
|
|
if (av_open_input_file(&g_psi.p_format_ctx, filename, NULL, 0, NULL))
|
|
{
|
|
printf("ERROR opening %s\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
/* now get the real stream info */
|
|
//if (avformat_find_stream_info(g_psi.p_format_ctx, NULL) < 0)
|
|
if (av_find_stream_info(g_psi.p_format_ctx) < 0)
|
|
{
|
|
printf("ERROR reading stream info\n");
|
|
return -1;
|
|
}
|
|
|
|
#if 1
|
|
/* print media info to standard out */
|
|
av_dump_format(g_psi.p_format_ctx, 0, filename, 0);
|
|
#endif
|
|
|
|
printf("nb_streams %d\n", g_psi.p_format_ctx->nb_streams);
|
|
|
|
g_audio_index = -1;
|
|
g_video_index = -1;
|
|
|
|
/* find first audio / video stream */
|
|
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_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_psi.p_format_ctx->streams[i]->codec->codec_id ==
|
|
CODEC_ID_AAC &&
|
|
g_audio_index < 0)
|
|
{
|
|
g_audio_index = i;
|
|
}
|
|
}
|
|
|
|
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);
|
|
//avformat_close_input(&g_psi.p_format_ctx);
|
|
av_close_input_file(g_psi.p_format_ctx);
|
|
return -1;
|
|
}
|
|
|
|
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[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);
|
|
|
|
if (g_psi.p_audio_codec == NULL)
|
|
{
|
|
printf("ERROR: audio codec not supported\n");
|
|
}
|
|
|
|
/* find decoder for video stream */
|
|
g_psi.p_video_codec =
|
|
avcodec_find_decoder(g_psi.p_video_codec_ctx->codec_id);
|
|
|
|
if (g_psi.p_video_codec == NULL)
|
|
{
|
|
printf("ERROR: video codec not supported\n");
|
|
}
|
|
|
|
/* open decoder for audio stream */
|
|
//if (avcodec_open2(g_psi.p_audio_codec_ctx, g_psi.p_audio_codec,
|
|
// NULL) < 0)
|
|
if (avcodec_open(g_psi.p_audio_codec_ctx, g_psi.p_audio_codec) < 0)
|
|
{
|
|
printf("ERROR: could not open audio decoder\n");
|
|
return -1;
|
|
}
|
|
|
|
printf("%d\n", g_psi.p_audio_codec_ctx->extradata_size);
|
|
hexdump(g_psi.p_audio_codec_ctx->extradata,
|
|
g_psi.p_audio_codec_ctx->extradata_size);
|
|
printf("%d %d %d %d\n", g_psi.p_audio_codec_ctx->sample_rate,
|
|
g_psi.p_audio_codec_ctx->bit_rate,
|
|
g_psi.p_audio_codec_ctx->channels,
|
|
g_psi.p_audio_codec_ctx->block_align);
|
|
|
|
/* open decoder for video stream */
|
|
//if (avcodec_open2(g_psi.p_video_codec_ctx, g_psi.p_video_codec,
|
|
// NULL) < 0)
|
|
if (avcodec_open(g_psi.p_video_codec_ctx, g_psi.p_video_codec) < 0)
|
|
{
|
|
printf("ERROR: could not open video decoder\n");
|
|
return -1;
|
|
}
|
|
|
|
g_psi.bsfc = av_bitstream_filter_init("h264_mp4toannexb");
|
|
printf("g_psi.bsfc %p\n", g_psi.bsfc);
|
|
|
|
if (xrdpvr_set_video_format(channel, 101, 0,
|
|
g_psi.p_video_codec_ctx->width,
|
|
g_psi.p_video_codec_ctx->height))
|
|
{
|
|
printf("xrdpvr_set_video_format() failed\n");
|
|
return -1;
|
|
}
|
|
|
|
printf("xrdpvr_play_media: calling xrdpvr_set_audio_format\n");
|
|
if (xrdpvr_set_audio_format(channel, 101, 0,
|
|
g_psi.p_audio_codec_ctx->extradata,
|
|
g_psi.p_audio_codec_ctx->extradata_size,
|
|
g_psi.p_audio_codec_ctx->sample_rate,
|
|
g_psi.p_audio_codec_ctx->bit_rate,
|
|
g_psi.p_audio_codec_ctx->channels,
|
|
g_psi.p_audio_codec_ctx->block_align))
|
|
{
|
|
printf("xrdpvr_set_audio_format() failed\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
double dts;
|
|
int error;
|
|
AVBitStreamFilterContext *bsfc;
|
|
AVPacket new_pkt;
|
|
|
|
//printf("xrdpvr_get_frame:\n");
|
|
/* 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;
|
|
|
|
if (firstAudioPkt)
|
|
{
|
|
firstAudioPkt = 0;
|
|
}
|
|
}
|
|
else if (av_pkt->stream_index == g_video_index)
|
|
{
|
|
|
|
bsfc = g_psi.bsfc;
|
|
while (bsfc != 0)
|
|
{
|
|
new_pkt = *av_pkt;
|
|
error = av_bitstream_filter_filter(bsfc, g_psi.p_video_codec_ctx, 0,
|
|
&new_pkt.data, &new_pkt.size,
|
|
av_pkt->data, av_pkt->size,
|
|
av_pkt->flags & PKT_FLAG_KEY);
|
|
if (error > 0)
|
|
{
|
|
av_free_packet(av_pkt);
|
|
new_pkt.destruct = av_destruct_packet;
|
|
}
|
|
else if (error < 0)
|
|
{
|
|
printf("bitstream filter error\n");
|
|
}
|
|
*av_pkt = new_pkt;
|
|
bsfc = bsfc->next;
|
|
}
|
|
|
|
dts = 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;
|
|
}
|
|
else
|
|
{
|
|
*delay_in_us = (int) ((dts - g_psi.videoTimeout) * 1000000);
|
|
g_psi.videoTimeout = dts;
|
|
}
|
|
*is_video_frame = 1;
|
|
|
|
if (firstVideoPkt)
|
|
{
|
|
firstVideoPkt = 0;
|
|
}
|
|
}
|
|
|
|
*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;
|
|
int error;
|
|
AVBitStreamFilterContext *bsfc;
|
|
AVPacket new_pkt;
|
|
|
|
//printf("xrdpvr_play_frame:\n");
|
|
|
|
if (av_read_frame(g_psi.p_format_ctx, &av_pkt) < 0)
|
|
{
|
|
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);
|
|
|
|
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)
|
|
{
|
|
bsfc = g_psi.bsfc;
|
|
while (bsfc != 0)
|
|
{
|
|
new_pkt = av_pkt;
|
|
error = av_bitstream_filter_filter(bsfc, g_psi.p_video_codec_ctx, 0,
|
|
&new_pkt.data, &new_pkt.size,
|
|
av_pkt.data, av_pkt.size,
|
|
av_pkt.flags & PKT_FLAG_KEY);
|
|
if (error > 0)
|
|
{
|
|
av_free_packet(&av_pkt);
|
|
new_pkt.destruct = av_destruct_packet;
|
|
}
|
|
else if (error < 0)
|
|
{
|
|
printf("bitstream filter error\n");
|
|
}
|
|
av_pkt = new_pkt;
|
|
bsfc = bsfc->next;
|
|
}
|
|
|
|
xrdpvr_send_video_data(channel, stream_id, av_pkt.size, av_pkt.data);
|
|
|
|
dts = av_pkt.dts;
|
|
dts *= av_q2d(g_psi.p_format_ctx->streams[g_video_index]->time_base);
|
|
|
|
*videoTimeout = (int) ((dts - g_psi.videoTimeout) * 1000000);
|
|
*audioTimeout = -1;
|
|
|
|
if (g_psi.videoTimeout > dts)
|
|
{
|
|
g_psi.videoTimeout = dts;
|
|
delay_in_us = 1000 * 40;
|
|
}
|
|
else
|
|
{
|
|
delay_in_us = (int) ((dts - g_psi.videoTimeout) * 1000000);
|
|
g_psi.videoTimeout = dts;
|
|
}
|
|
|
|
printf("video delay: %d\n", delay_in_us);
|
|
usleep(delay_in_us);
|
|
}
|
|
|
|
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");
|
|
|
|
g_psi.audioTimeout = -1;
|
|
g_psi.videoTimeout = -1;
|
|
|
|
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;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
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
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_set_video_format(void *channel, uint32_t stream_id, int format,
|
|
int width, int height)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
width = (width + 15) & ~15;
|
|
stream_new(s, MAX_PDU_SIZE);
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_SET_VIDEO_FORMAT);
|
|
stream_ins_u32_le(s, stream_id);
|
|
stream_ins_u32_le(s, format);
|
|
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 audio format
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_set_audio_format(void *channel, uint32_t stream_id, int format,
|
|
char *extradata, int extradata_size, int sample_rate,
|
|
int bit_rate, int channels, int block_align)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
stream_new(s, MAX_PDU_SIZE);
|
|
|
|
printf("extradata_size %d sample_rate %d bit_rate %d channels %d "
|
|
"block_align %d\n", extradata_size, sample_rate, bit_rate,
|
|
channels, block_align);
|
|
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_SET_AUDIO_FORMAT);
|
|
stream_ins_u32_le(s, stream_id);
|
|
stream_ins_u32_le(s, format);
|
|
stream_ins_u32_le(s, extradata_size);
|
|
memcpy(s->p, extradata, extradata_size);
|
|
s->p += extradata_size;
|
|
stream_ins_u32_le(s, sample_rate);
|
|
stream_ins_u32_le(s, bit_rate);
|
|
stream_ins_u32_le(s, channels);
|
|
stream_ins_u32_le(s, block_align);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* send video data to client
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
* @param data_len number of bytes to send
|
|
* @param data video data to send
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_send_video_data(void *channel, uint32_t stream_id,
|
|
uint32_t data_len, uint8_t *data)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
//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 */
|
|
stream_ins_u32_le(s, CMD_SEND_VIDEO_DATA);
|
|
stream_ins_u32_le(s, stream_id);
|
|
stream_ins_u32_le(s, data_len);
|
|
stream_ins_byte_array(s, data, data_len);
|
|
|
|
/* 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);
|
|
|
|
#ifdef DEBUG_FRAGMENTS
|
|
printf("### sent %d + 4 bytes video data to client\n", len);
|
|
#endif
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* send audio data to client
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param stream_id unique identification number for this stream
|
|
* @param data_len number of bytes to send
|
|
* @param data audio data to send
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_send_audio_data(void *channel, uint32_t stream_id, uint32_t data_len,
|
|
uint8_t *data)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
//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 */
|
|
stream_ins_u32_le(s, CMD_SEND_AUDIO_DATA);
|
|
stream_ins_u32_le(s, stream_id);
|
|
stream_ins_u32_le(s, data_len);
|
|
stream_ins_byte_array(s, data, data_len);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* send media meta-data to client
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param filename media file
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*****************************************************************************/
|
|
int
|
|
xrdpvr_create_metadata_file(void *channel, char *filename)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
int fd;
|
|
|
|
if ((fd = open(filename, O_RDONLY)) < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
stream_new(s, MAX_PDU_SIZE + 1048576);
|
|
|
|
/* send CMD_CREATE_META_DATA_FILE */
|
|
stream_ins_u32_le(s, 4); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_CREATE_META_DATA_FILE);
|
|
|
|
if (xrdpvr_write_to_client(channel, s))
|
|
{
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
/* read first 1MB of file and send to client */
|
|
s->p = s->data;
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_WRITE_META_DATA);
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
|
|
if ((rv = read(fd, s->p, 1048576)) <= 0)
|
|
{
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
s->p += rv;
|
|
|
|
/* insert number of bytes in stream */
|
|
len = stream_length(s) - 4;
|
|
cptr = s->p;
|
|
s->p = s->data;
|
|
stream_ins_u32_le(s, len); /* number of bytes in this cmd */
|
|
s->p += 4;
|
|
stream_ins_u32_le(s, rv); /* number of metadata bytes */
|
|
s->p = cptr;
|
|
|
|
/* write data to virtual channel */
|
|
rv = xrdpvr_write_to_client(channel, s);
|
|
|
|
/* send CMD_CLOSE_META_DATA_FILE */
|
|
s->p = s->data;
|
|
stream_ins_u32_le(s, 4); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_CLOSE_META_DATA_FILE);
|
|
|
|
if (xrdpvr_write_to_client(channel, s))
|
|
{
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
stream_free(s);
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
******************************************************************************/
|
|
static int
|
|
xrdpvr_read_from_client(void *channel, STREAM *s, int bytes, int timeout)
|
|
{
|
|
unsigned int bytes_read;
|
|
int total_read;
|
|
int ok;
|
|
|
|
//printf("xrdpvr_read_from_client:\n");
|
|
total_read = 0;
|
|
while (total_read < bytes)
|
|
{
|
|
//printf("xrdpvr_read_from_client: loop\n");
|
|
bytes_read = bytes - total_read;
|
|
ok = WTSVirtualChannelRead(channel, timeout, s->p, bytes_read,
|
|
&bytes_read);
|
|
//printf("xrdpvr_read_from_client: loop ok %d\n", ok);
|
|
if (ok)
|
|
{
|
|
//printf("xrdpvr_read_from_client: bytes_read %d\n", bytes_read);
|
|
total_read += bytes_read;
|
|
s->p += bytes_read;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* write data to a xrdpvr client
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param s structure containing data to write
|
|
*
|
|
* @return 0 on success, -1 on failure
|
|
******************************************************************************/
|
|
static int
|
|
xrdpvr_write_to_client(void *channel, STREAM *s)
|
|
{
|
|
int bytes_to_send;
|
|
int bytes_written;
|
|
int index = 0;
|
|
int rv;
|
|
|
|
if ((channel == NULL) || (s == NULL))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
bytes_to_send = stream_length(s);
|
|
|
|
while (1)
|
|
{
|
|
rv = WTSVirtualChannelWrite(channel, &s->data[index], bytes_to_send,
|
|
&bytes_written);
|
|
|
|
if (rv == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
index += bytes_written;
|
|
bytes_to_send -= bytes_written;
|
|
|
|
usleep(1000 * 3);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* write set volume to a xrdpvr client
|
|
*
|
|
* @param channel opaque handle returned by WTSVirtualChannelOpenEx
|
|
* @param volume volume 0x0000 to 0xffff
|
|
*
|
|
* @return 0 on success, -1 on failure
|
|
******************************************************************************/
|
|
int
|
|
xrdpvr_set_volume(void *channel, int volume)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
stream_new(s, MAX_BUFSIZE);
|
|
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_SET_VOLUME);
|
|
stream_ins_u32_le(s, volume);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
int
|
|
xrdpvr_send_init(void *channel)
|
|
{
|
|
STREAM *s;
|
|
char *cptr;
|
|
int rv;
|
|
int len;
|
|
|
|
printf("xrdpvr_send_init:\n");
|
|
stream_new(s, MAX_BUFSIZE);
|
|
|
|
stream_ins_u32_le(s, 0); /* number of bytes to follow */
|
|
stream_ins_u32_le(s, CMD_INIT_XRDPVR);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
}
|