diff options
Diffstat (limited to '')
29 files changed, 7975 insertions, 0 deletions
diff --git a/channels/tsmf/CMakeLists.txt b/channels/tsmf/CMakeLists.txt new file mode 100644 index 0000000..8b4073e --- /dev/null +++ b/channels/tsmf/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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. + +define_channel("tsmf") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/tsmf/ChannelOptions.cmake b/channels/tsmf/ChannelOptions.cmake new file mode 100644 index 0000000..b5252ea --- /dev/null +++ b/channels/tsmf/ChannelOptions.cmake @@ -0,0 +1,23 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +if(ANDROID) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options(NAME "tsmf" TYPE "dynamic" + DESCRIPTION "[DEPRECATED] Video Redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEV]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/tsmf/client/CMakeLists.txt b/channels/tsmf/client/CMakeLists.txt new file mode 100644 index 0000000..ea8e2f0 --- /dev/null +++ b/channels/tsmf/client/CMakeLists.txt @@ -0,0 +1,84 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# 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. + +define_channel_client("tsmf") + +message(DEPRECATION "TSMF channel is no longer maintained. Use [MS-RDPEVOR] (/video) instead.") + + +find_package(PkgConfig) +if (PkgConfig_FOUND) + pkg_check_modules(gstreamer gstreamer-1.0) +endif() + +if (WITH_GSTREAMER_1_0) + if(gstreamer_FOUND) + add_definitions(-DWITH_GSTREAMER_1_0) + else() + message(WARNING "gstreamer not detected, disabling support") + endif() +endif() + +set(${MODULE_PREFIX}_SRCS + tsmf_audio.c + tsmf_audio.h + tsmf_codec.c + tsmf_codec.h + tsmf_constants.h + tsmf_decoder.c + tsmf_decoder.h + tsmf_ifman.c + tsmf_ifman.h + tsmf_main.c + tsmf_main.h + tsmf_media.c + tsmf_media.h + tsmf_types.h +) + +set(${MODULE_PREFIX}_LIBS + winpr freerdp +) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if(WITH_VIDEO_FFMPEG) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ffmpeg" "decoder") +endif() + +if(WITH_GSTREAMER_1_0) + find_package(X11) + if (X11_Xrandr_FOUND) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "gstreamer" "decoder") + else() + message(WARNING "Disabling tsmf gstreamer because XRandR wasn't found") + endif() +endif() + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "audio") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "audio") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "audio") +endif() diff --git a/channels/tsmf/client/alsa/CMakeLists.txt b/channels/tsmf/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..9910542 --- /dev/null +++ b/channels/tsmf/client/alsa/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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. + +define_channel_client_subsystem("tsmf" "alsa" "audio") + +find_package(ALSA REQUIRED) + +set(${MODULE_PREFIX}_SRCS + tsmf_alsa.c +) + +set(${MODULE_PREFIX}_LIBS + winpr + freerdp + ${ALSA_LIBRARIES} +) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/channels/tsmf/client/alsa/tsmf_alsa.c b/channels/tsmf/client/alsa/tsmf_alsa.c new file mode 100644 index 0000000..fcf30aa --- /dev/null +++ b/channels/tsmf/client/alsa/tsmf_alsa.c @@ -0,0 +1,240 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - ALSA Audio Device + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <winpr/crt.h> + +#include <alsa/asoundlib.h> + +#include <freerdp/types.h> +#include <freerdp/codec/dsp.h> + +#include "tsmf_audio.h" + +typedef struct +{ + ITSMFAudioDevice iface; + + char device[32]; + snd_pcm_t* out_handle; + UINT32 source_rate; + UINT32 actual_rate; + UINT32 source_channels; + UINT32 actual_channels; + UINT32 bytes_per_sample; +} TSMFAlsaAudioDevice; + +static BOOL tsmf_alsa_open_device(TSMFAlsaAudioDevice* alsa) +{ + int error = 0; + error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0); + + if (error < 0) + { + WLog_ERR(TAG, "failed to open device %s", alsa->device); + return FALSE; + } + + DEBUG_TSMF("open device %s", alsa->device); + return TRUE; +} + +static BOOL tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + + if (!device) + { + strncpy(alsa->device, "default", sizeof(alsa->device)); + } + else + { + strncpy(alsa->device, device, sizeof(alsa->device) - 1); + } + + return tsmf_alsa_open_device(alsa); +} + +static BOOL tsmf_alsa_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample) +{ + int error = 0; + snd_pcm_uframes_t frames = 0; + snd_pcm_hw_params_t* hw_params = NULL; + snd_pcm_sw_params_t* sw_params = NULL; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + + if (!alsa->out_handle) + return FALSE; + + snd_pcm_drop(alsa->out_handle); + alsa->actual_rate = alsa->source_rate = sample_rate; + alsa->actual_channels = alsa->source_channels = channels; + alsa->bytes_per_sample = bits_per_sample / 8; + error = snd_pcm_hw_params_malloc(&hw_params); + + if (error < 0) + { + WLog_ERR(TAG, "snd_pcm_hw_params_malloc failed"); + return FALSE; + } + + snd_pcm_hw_params_any(alsa->out_handle, hw_params); + snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, &alsa->actual_rate, NULL); + snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, &alsa->actual_channels); + frames = sample_rate; + snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, &frames); + snd_pcm_hw_params(alsa->out_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + error = snd_pcm_sw_params_malloc(&sw_params); + + if (error < 0) + { + WLog_ERR(TAG, "snd_pcm_sw_params_malloc"); + return FALSE; + } + + snd_pcm_sw_params_current(alsa->out_handle, sw_params); + snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params, frames / 2); + snd_pcm_sw_params(alsa->out_handle, sw_params); + snd_pcm_sw_params_free(sw_params); + snd_pcm_prepare(alsa->out_handle); + DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "", + sample_rate, channels, bits_per_sample); + DEBUG_TSMF("hardware buffer %lu frames", frames); + + if ((alsa->actual_rate != alsa->source_rate) || + (alsa->actual_channels != alsa->source_channels)) + { + DEBUG_TSMF("actual rate %" PRIu32 " / channel %" PRIu32 " is different " + "from source rate %" PRIu32 " / channel %" PRIu32 ", resampling required.", + alsa->actual_rate, alsa->actual_channels, alsa->source_rate, + alsa->source_channels); + } + + return TRUE; +} + +static BOOL tsmf_alsa_play(ITSMFAudioDevice* audio, const BYTE* src, UINT32 data_size) +{ + int len = 0; + int error = 0; + int frames = 0; + const BYTE* end = NULL; + const BYTE* pindex = NULL; + int rbytes_per_frame = 0; + int sbytes_per_frame = 0; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + DEBUG_TSMF("data_size %" PRIu32 "", data_size); + + if (alsa->out_handle) + { + sbytes_per_frame = alsa->source_channels * alsa->bytes_per_sample; + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_sample; + pindex = src; + end = pindex + data_size; + + while (pindex < end) + { + len = end - pindex; + frames = len / rbytes_per_frame; + error = snd_pcm_writei(alsa->out_handle, pindex, frames); + + if (error == -EPIPE) + { + snd_pcm_recover(alsa->out_handle, error, 0); + error = 0; + } + else if (error < 0) + { + DEBUG_TSMF("error len %d", error); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + tsmf_alsa_open_device(alsa); + break; + } + + DEBUG_TSMF("%d frames played.", error); + + if (error == 0) + break; + + pindex += error * rbytes_per_frame; + } + } + + return TRUE; +} + +static UINT64 tsmf_alsa_get_latency(ITSMFAudioDevice* audio) +{ + UINT64 latency = 0; + snd_pcm_sframes_t frames = 0; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + + if (alsa->out_handle && alsa->actual_rate > 0 && + snd_pcm_delay(alsa->out_handle, &frames) == 0 && frames > 0) + { + latency = ((UINT64)frames) * 10000000LL / (UINT64)alsa->actual_rate; + } + + return latency; +} + +static BOOL tsmf_alsa_flush(ITSMFAudioDevice* audio) +{ + return TRUE; +} + +static void tsmf_alsa_free(ITSMFAudioDevice* audio) +{ + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + DEBUG_TSMF(""); + + if (alsa->out_handle) + { + snd_pcm_drain(alsa->out_handle); + snd_pcm_close(alsa->out_handle); + } + + free(alsa); +} + +FREERDP_ENTRY_POINT(ITSMFAudioDevice* alsa_freerdp_tsmf_client_audio_subsystem_entry(void)) +{ + TSMFAlsaAudioDevice* alsa = calloc(1, sizeof(TSMFAlsaAudioDevice)); + if (!alsa) + return NULL; + + alsa->iface.Open = tsmf_alsa_open; + alsa->iface.SetFormat = tsmf_alsa_set_format; + alsa->iface.Play = tsmf_alsa_play; + alsa->iface.GetLatency = tsmf_alsa_get_latency; + alsa->iface.Flush = tsmf_alsa_flush; + alsa->iface.Free = tsmf_alsa_free; + return &alsa->iface; +} diff --git a/channels/tsmf/client/ffmpeg/CMakeLists.txt b/channels/tsmf/client/ffmpeg/CMakeLists.txt new file mode 100644 index 0000000..a50bed0 --- /dev/null +++ b/channels/tsmf/client/ffmpeg/CMakeLists.txt @@ -0,0 +1,42 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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. + +define_channel_client_subsystem("tsmf" "ffmpeg" "decoder") + +set(${MODULE_PREFIX}_SRCS + tsmf_ffmpeg.c +) + +set(${MODULE_PREFIX}_LIBS + freerdp + ${FFMPEG_LIBRARIES} +) +if(APPLE) + # For this to work on apple, we need to add some frameworks + FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation) + FIND_LIBRARY(COREVIDEO_LIBRARY CoreVideo) + FIND_LIBRARY(COREVIDEODECODE_LIBRARY VideoDecodeAcceleration) + + list(APPEND ${MODULE_PREFIX}_LIBS ${COREFOUNDATION_LIBRARY} ${COREVIDEO_LIBRARY} ${COREVIDEODECODE_LIBRARY}) +endif() + +include_directories(..) +include_directories(${FFMPEG_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + diff --git a/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c new file mode 100644 index 0000000..eb45a8e --- /dev/null +++ b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c @@ -0,0 +1,695 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - FFmpeg Decoder + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> + +#include <freerdp/channels/log.h> +#include <freerdp/client/tsmf.h> + +#include <libavcodec/avcodec.h> +#include <libavutil/common.h> +#include <libavutil/cpu.h> +#include <libavutil/imgutils.h> + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +/* Compatibility with older FFmpeg */ +#if LIBAVUTIL_VERSION_MAJOR < 50 +#define AVMEDIA_TYPE_VIDEO 0 +#define AVMEDIA_TYPE_AUDIO 1 +#endif + +#if LIBAVCODEC_VERSION_MAJOR < 54 +#define MAX_AUDIO_FRAME_SIZE AVCODEC_MAX_AUDIO_FRAME_SIZE +#else +#define MAX_AUDIO_FRAME_SIZE 192000 +#endif + +#if LIBAVCODEC_VERSION_MAJOR < 55 +#define AV_CODEC_ID_VC1 CODEC_ID_VC1 +#define AV_CODEC_ID_WMAV2 CODEC_ID_WMAV2 +#define AV_CODEC_ID_WMAPRO CODEC_ID_WMAPRO +#define AV_CODEC_ID_MP3 CODEC_ID_MP3 +#define AV_CODEC_ID_MP2 CODEC_ID_MP2 +#define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO +#define AV_CODEC_ID_WMV3 CODEC_ID_WMV3 +#define AV_CODEC_ID_AAC CODEC_ID_AAC +#define AV_CODEC_ID_H264 CODEC_ID_H264 +#define AV_CODEC_ID_AC3 CODEC_ID_AC3 +#endif + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2) +#define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED +#define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED +#endif + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#endif + +typedef struct +{ + ITSMFDecoder iface; + + int media_type; +#if LIBAVCODEC_VERSION_MAJOR < 55 + enum CodecID codec_id; +#else + enum AVCodecID codec_id; +#endif + AVCodecContext* codec_context; + AVCodec* codec; + AVFrame* frame; + int prepared; + + BYTE* decoded_data; + UINT32 decoded_size; + UINT32 decoded_size_max; +} TSMFFFmpegDecoder; + +static BOOL tsmf_ffmpeg_init_context(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec_context = avcodec_alloc_context3(NULL); + + if (!mdecoder->codec_context) + { + WLog_ERR(TAG, "avcodec_alloc_context failed."); + return FALSE; + } + + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_video_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec_context->width = media_type->Width; + mdecoder->codec_context->height = media_type->Height; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->time_base.den = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->time_base.num = media_type->SamplesPerSecond.Denominator; +#if LIBAVCODEC_VERSION_MAJOR < 55 + mdecoder->frame = avcodec_alloc_frame(); +#else + mdecoder->frame = av_frame_alloc(); +#endif + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_audio_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec_context->sample_rate = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->channels = media_type->Channels; + mdecoder->codec_context->block_align = media_type->BlockAlign; +#if LIBAVCODEC_VERSION_MAJOR < 55 +#ifdef AV_CPU_FLAG_SSE2 + mdecoder->codec_context->dsp_mask = AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMX2; +#else +#if LIBAVCODEC_VERSION_MAJOR < 53 + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMXEXT; +#else + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMX2; +#endif +#endif +#else /* LIBAVCODEC_VERSION_MAJOR < 55 */ +#ifdef AV_CPU_FLAG_SSE2 +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 17, 100) + av_set_cpu_flags_mask(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT); +#else + av_force_cpu_flags(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT); +#endif +#else + av_set_cpu_flags_mask(FF_MM_SSE2 | FF_MM_MMX2); +#endif +#endif /* LIBAVCODEC_VERSION_MAJOR < 55 */ + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + BYTE* p = NULL; + UINT32 size = 0; + const BYTE* s = NULL; + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id); + + if (!mdecoder->codec) + { + WLog_ERR(TAG, "avcodec_find_decoder failed."); + return FALSE; + } + + mdecoder->codec_context->codec_id = mdecoder->codec_id; + mdecoder->codec_context->codec_type = mdecoder->media_type; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + if (!tsmf_ffmpeg_init_video_stream(decoder, media_type)) + return FALSE; + + break; + + case AVMEDIA_TYPE_AUDIO: + if (!tsmf_ffmpeg_init_audio_stream(decoder, media_type)) + return FALSE; + + break; + + default: + WLog_ERR(TAG, "unknown media_type %d", mdecoder->media_type); + break; + } + + if (media_type->ExtraData) + { + /* Add a padding to avoid invalid memory read in some codec */ + mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8; + mdecoder->codec_context->extradata = calloc(1, mdecoder->codec_context->extradata_size); + + if (!mdecoder->codec_context->extradata) + return FALSE; + + if (media_type->SubType == TSMF_SUB_TYPE_AVC1 && + media_type->FormatType == TSMF_FORMAT_TYPE_MPEG2VIDEOINFO) + { + size_t required = 6; + /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska. + See http://haali.su/mkv/codecs.pdf */ + p = mdecoder->codec_context->extradata; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + *p++ = 1; /* Reserved? */ + *p++ = media_type->ExtraData[8]; /* Profile */ + *p++ = 0; /* Profile */ + *p++ = media_type->ExtraData[12]; /* Level */ + *p++ = 0xff; /* Flag? */ + *p++ = 0xe0 | 0x01; /* Reserved | #sps */ + s = media_type->ExtraData + 20; + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + required += size + 2; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + memcpy(p, s, size + 2); + s += size + 2; + p += size + 2; + required++; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + *p++ = 1; /* #pps */ + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + required += size + 2; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + memcpy(p, s, size + 2); + } + else + { + memcpy(mdecoder->codec_context->extradata, media_type->ExtraData, + media_type->ExtraDataSize); + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < + media_type->ExtraDataSize + 8ull)) + return FALSE; + memset(mdecoder->codec_context->extradata + media_type->ExtraDataSize, 0, 8); + } + } + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 18, 100) + if (mdecoder->codec->capabilities & AV_CODEC_CAP_TRUNCATED) + mdecoder->codec_context->flags |= AV_CODEC_FLAG_TRUNCATED; +#endif + + return TRUE; +} + +static BOOL tsmf_ffmpeg_prepare(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (avcodec_open2(mdecoder->codec_context, mdecoder->codec, NULL) < 0) + { + WLog_ERR(TAG, "avcodec_open2 failed."); + return FALSE; + } + + mdecoder->prepared = 1; + return TRUE; +} + +static BOOL tsmf_ffmpeg_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + WINPR_ASSERT(mdecoder); + WINPR_ASSERT(media_type); + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = AVMEDIA_TYPE_VIDEO; + break; + + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = AVMEDIA_TYPE_AUDIO; + break; + + default: + return FALSE; + } + + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->codec_id = AV_CODEC_ID_VC1; + break; + + case TSMF_SUB_TYPE_WMA2: + mdecoder->codec_id = AV_CODEC_ID_WMAV2; + break; + + case TSMF_SUB_TYPE_WMA9: + mdecoder->codec_id = AV_CODEC_ID_WMAPRO; + break; + + case TSMF_SUB_TYPE_MP3: + mdecoder->codec_id = AV_CODEC_ID_MP3; + break; + + case TSMF_SUB_TYPE_MP2A: + mdecoder->codec_id = AV_CODEC_ID_MP2; + break; + + case TSMF_SUB_TYPE_MP2V: + mdecoder->codec_id = AV_CODEC_ID_MPEG2VIDEO; + break; + + case TSMF_SUB_TYPE_WMV3: + mdecoder->codec_id = AV_CODEC_ID_WMV3; + break; + + case TSMF_SUB_TYPE_AAC: + mdecoder->codec_id = AV_CODEC_ID_AAC; + + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + if (media_type->ExtraDataSize < 12) + return FALSE; + + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + + break; + + case TSMF_SUB_TYPE_H264: + case TSMF_SUB_TYPE_AVC1: + mdecoder->codec_id = AV_CODEC_ID_H264; + break; + + case TSMF_SUB_TYPE_AC3: + mdecoder->codec_id = AV_CODEC_ID_AC3; + break; + + default: + return FALSE; + } + + if (!tsmf_ffmpeg_init_context(decoder)) + return FALSE; + + if (!tsmf_ffmpeg_init_stream(decoder, media_type)) + return FALSE; + + if (!tsmf_ffmpeg_prepare(decoder)) + return FALSE; + + return TRUE; +} + +static BOOL tsmf_ffmpeg_decode_video(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + int decoded = 0; + int len = 0; + AVFrame* frame = NULL; + BOOL ret = TRUE; +#if LIBAVCODEC_VERSION_MAJOR < 52 || \ + (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_video(mdecoder->codec_context, mdecoder->frame, &decoded, data, data_size); +#else + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (BYTE*)data; + pkt.size = data_size; + + if (extensions & TSMM_SAMPLE_EXT_CLEANPOINT) + pkt.flags |= AV_PKT_FLAG_KEY; + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101) + len = avcodec_decode_video2(mdecoder->codec_context, mdecoder->frame, &decoded, &pkt); +#else + len = avcodec_send_packet(mdecoder->codec_context, &pkt); + if (len > 0) + { + do + { + len = avcodec_receive_frame(mdecoder->codec_context, mdecoder->frame); + } while (len == AVERROR(EAGAIN)); + } +#endif + } +#endif + + if (len < 0) + { + WLog_ERR(TAG, "data_size %" PRIu32 ", avcodec_decode_video failed (%d)", data_size, len); + ret = FALSE; + } + else if (!decoded) + { + WLog_ERR(TAG, "data_size %" PRIu32 ", no frame is decoded.", data_size); + ret = FALSE; + } + else + { + DEBUG_TSMF("linesize[0] %d linesize[1] %d linesize[2] %d linesize[3] %d " + "pix_fmt %d width %d height %d", + mdecoder->frame->linesize[0], mdecoder->frame->linesize[1], + mdecoder->frame->linesize[2], mdecoder->frame->linesize[3], + mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width, + mdecoder->codec_context->height); + mdecoder->decoded_size = av_image_get_buffer_size(mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, + mdecoder->codec_context->height, 1); + mdecoder->decoded_data = calloc(1, mdecoder->decoded_size); + + if (!mdecoder->decoded_data) + return FALSE; + +#if LIBAVCODEC_VERSION_MAJOR < 55 + frame = avcodec_alloc_frame(); +#else + frame = av_frame_alloc(); +#endif + av_image_fill_arrays(frame->data, frame->linesize, mdecoder->decoded_data, + mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width, + mdecoder->codec_context->height, 1); + av_image_copy(frame->data, frame->linesize, (const uint8_t**)mdecoder->frame->data, + mdecoder->frame->linesize, mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + av_free(frame); + } + + return ret; +} + +static BOOL tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + int len = 0; + int frame_size = 0; + UINT32 src_size = 0; + const BYTE* src = NULL; + BYTE* dst = NULL; + int dst_offset = 0; +#if 0 + WLog_DBG(TAG, ("tsmf_ffmpeg_decode_audio: data_size %"PRIu32"", data_size)); + + for (int i = 0; i < data_size; i++) + { + WLog_DBG(TAG, ("%02"PRIX8"", data[i])); + + if (i % 16 == 15) + WLog_DBG(TAG, ("\n")); + } + +#endif + + if (mdecoder->decoded_size_max == 0) + mdecoder->decoded_size_max = MAX_AUDIO_FRAME_SIZE + 16; + + mdecoder->decoded_data = calloc(1, mdecoder->decoded_size_max); + + if (!mdecoder->decoded_data) + return FALSE; + + /* align the memory for SSE2 needs */ + dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F); + dst_offset = dst - mdecoder->decoded_data; + src = data; + src_size = data_size; + + while (src_size > 0) + { + /* Ensure enough space for decoding */ + if (mdecoder->decoded_size_max - mdecoder->decoded_size < MAX_AUDIO_FRAME_SIZE) + { + BYTE* tmp_data = NULL; + tmp_data = realloc(mdecoder->decoded_data, mdecoder->decoded_size_max * 2 + 16); + + if (!tmp_data) + return FALSE; + + mdecoder->decoded_size_max = mdecoder->decoded_size_max * 2 + 16; + mdecoder->decoded_data = tmp_data; + dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F); + + if (dst - mdecoder->decoded_data != dst_offset) + { + /* re-align the memory if the alignment has changed after realloc */ + memmove(dst, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); + dst_offset = dst - mdecoder->decoded_data; + } + + dst += mdecoder->decoded_size; + } + + frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size; +#if LIBAVCODEC_VERSION_MAJOR < 52 || \ + (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_audio2(mdecoder->codec_context, (int16_t*)dst, &frame_size, src, + src_size); +#else + { +#if LIBAVCODEC_VERSION_MAJOR < 55 + AVFrame* decoded_frame = avcodec_alloc_frame(); +#else + AVFrame* decoded_frame = av_frame_alloc(); +#endif + int got_frame = 0; + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (BYTE*)src; + pkt.size = src_size; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101) + len = avcodec_decode_audio4(mdecoder->codec_context, decoded_frame, &got_frame, &pkt); +#else + len = avcodec_send_packet(mdecoder->codec_context, &pkt); + if (len > 0) + { + do + { + len = avcodec_receive_frame(mdecoder->codec_context, decoded_frame); + } while (len == AVERROR(EAGAIN)); + } +#endif + + if (len >= 0 && got_frame) + { + frame_size = av_samples_get_buffer_size(NULL, mdecoder->codec_context->channels, + decoded_frame->nb_samples, + mdecoder->codec_context->sample_fmt, 1); + memcpy(dst, decoded_frame->data[0], frame_size); + } + else + { + frame_size = 0; + } + + av_free(decoded_frame); + } +#endif + + if (len > 0) + { + src += len; + src_size -= len; + } + + if (frame_size > 0) + { + mdecoder->decoded_size += frame_size; + dst += frame_size; + } + } + + if (mdecoder->decoded_size == 0) + { + free(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + else if (dst_offset) + { + /* move the aligned decoded data to original place */ + memmove(mdecoder->decoded_data, mdecoder->decoded_data + dst_offset, + mdecoder->decoded_size); + } + + DEBUG_TSMF("data_size %" PRIu32 " decoded_size %" PRIu32 "", data_size, mdecoder->decoded_size); + return TRUE; +} + +static BOOL tsmf_ffmpeg_decode(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (mdecoder->decoded_data) + { + free(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + + mdecoder->decoded_size = 0; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + return tsmf_ffmpeg_decode_video(decoder, data, data_size, extensions); + + case AVMEDIA_TYPE_AUDIO: + return tsmf_ffmpeg_decode_audio(decoder, data, data_size, extensions); + + default: + WLog_ERR(TAG, "unknown media type."); + return FALSE; + } +} + +static BYTE* tsmf_ffmpeg_get_decoded_data(ITSMFDecoder* decoder, UINT32* size) +{ + BYTE* buf = NULL; + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + *size = mdecoder->decoded_size; + buf = mdecoder->decoded_data; + mdecoder->decoded_data = NULL; + mdecoder->decoded_size = 0; + return buf; +} + +static UINT32 tsmf_ffmpeg_get_decoded_format(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + switch (mdecoder->codec_context->pix_fmt) + { + case AV_PIX_FMT_YUV420P: + return RDP_PIXFMT_I420; + + default: + WLog_ERR(TAG, "unsupported pixel format %u", mdecoder->codec_context->pix_fmt); + return (UINT32)-1; + } +} + +static BOOL tsmf_ffmpeg_get_decoded_dimension(ITSMFDecoder* decoder, UINT32* width, UINT32* height) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (mdecoder->codec_context->width > 0 && mdecoder->codec_context->height > 0) + { + *width = mdecoder->codec_context->width; + *height = mdecoder->codec_context->height; + return TRUE; + } + else + { + return FALSE; + } +} + +static void tsmf_ffmpeg_free(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (mdecoder->frame) + av_free(mdecoder->frame); + + free(mdecoder->decoded_data); + + if (mdecoder->codec_context) + { + if (mdecoder->prepared) + avcodec_close(mdecoder->codec_context); + + free(mdecoder->codec_context->extradata); + av_free(mdecoder->codec_context); + } + + free(decoder); +} + +static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT; +static BOOL CALLBACK InitializeAvCodecs(PINIT_ONCE once, PVOID param, PVOID* context) +{ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) + avcodec_register_all(); +#endif + return TRUE; +} + +FREERDP_ENTRY_POINT(ITSMFDecoder* ffmpeg_freerdp_tsmf_client_decoder_subsystem_entry(void)) +{ + TSMFFFmpegDecoder* decoder = NULL; + InitOnceExecuteOnce(&g_Initialized, InitializeAvCodecs, NULL, NULL); + WLog_DBG(TAG, "TSMFDecoderEntry FFMPEG"); + decoder = (TSMFFFmpegDecoder*)calloc(1, sizeof(TSMFFFmpegDecoder)); + + if (!decoder) + return NULL; + + decoder->iface.SetFormat = tsmf_ffmpeg_set_format; + decoder->iface.Decode = tsmf_ffmpeg_decode; + decoder->iface.GetDecodedData = tsmf_ffmpeg_get_decoded_data; + decoder->iface.GetDecodedFormat = tsmf_ffmpeg_get_decoded_format; + decoder->iface.GetDecodedDimension = tsmf_ffmpeg_get_decoded_dimension; + decoder->iface.Free = tsmf_ffmpeg_free; + return (ITSMFDecoder*)decoder; +} diff --git a/channels/tsmf/client/gstreamer/CMakeLists.txt b/channels/tsmf/client/gstreamer/CMakeLists.txt new file mode 100644 index 0000000..9490cc0 --- /dev/null +++ b/channels/tsmf/client/gstreamer/CMakeLists.txt @@ -0,0 +1,76 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script for gstreamer subsystem +# +# (C) Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# 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. + +define_channel_client_subsystem("tsmf" "gstreamer" "decoder") + +if(NOT gstreamer_FOUND) + message(FATAL_ERROR "GStreamer library not found, but required for TSMF module.") +endif() + +set(SRC "tsmf_gstreamer.c") + +pkg_check_modules(gstreamerbase gstreamer-base-1.0 REQUIRED) +pkg_check_modules(gstreamervideo gstreamer-video-1.0 REQUIRED) +pkg_check_modules(gstreamerapp gstreamer-app-1.0 REQUIRED) + +set(LIBS + ${gstreamer_LIBRARIES} + ${gstreamerbase_LIBRARIES} + ${gstreamervideo_LIBRARIES} + ${gstreamerapp_LIBRARIES} +) +include_directories( + ${gstreamer_INCLUDE_DIRS} + ${gstreamerbase_INCLUDE_DIRS} + ${gstreamervideo_INCLUDE_DIRS} + ${gstreamerapp_INCLUDE_DIRS} +) + + +if(ANDROID) + set(SRC ${SRC} + tsmf_android.c) +else() + find_package(X11 REQUIRED) + + list(APPEND SRC + tsmf_X11.c) + list(APPEND LIBS + ${X11_LIBRARIES} + ${X11_Xext_LIB}) + if (NOT APPLE) + list(APPEND LIBS rt) + endif() + + if(X11_Xext_FOUND) + add_definitions(-DWITH_XEXT=1) + endif() + +endif() + +set(${MODULE_PREFIX}_SRCS + "${SRC}" +) + +set(${MODULE_PREFIX}_LIBS + ${LIBS} + winpr +) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/channels/tsmf/client/gstreamer/tsmf_X11.c b/channels/tsmf/client/gstreamer/tsmf_X11.c new file mode 100644 index 0000000..987e69b --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_X11.c @@ -0,0 +1,500 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder X11 specifics + * + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak <armin.novak@thincast.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. + */ + +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#ifndef __CYGWIN__ +#include <sys/syscall.h> +#endif + +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <err.h> +#include <errno.h> +#include <winpr/thread.h> +#include <winpr/string.h> +#include <winpr/platform.h> + +#include <gst/gst.h> + +#if GST_VERSION_MAJOR > 0 +#include <gst/video/videooverlay.h> +#else +#include <gst/interfaces/xoverlay.h> +#endif + +#include <X11/Xlib.h> +#include <X11/extensions/Xrandr.h> +#include <X11/extensions/shape.h> + +#include <freerdp/channels/tsmf.h> + +#include "tsmf_platform.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +#if !defined(WITH_XEXT) +#warning "Building TSMF without shape extension support" +#endif + +struct X11Handle +{ + int shmid; + int* xfwin; +#if defined(WITH_XEXT) + BOOL has_shape; +#endif + Display* disp; + Window subwin; + BOOL subwinMapped; +#if GST_VERSION_MAJOR > 0 + GstVideoOverlay* overlay; +#else + GstXOverlay* overlay; +#endif + int subwinWidth; + int subwinHeight; + int subwinX; + int subwinY; +}; + +static const char* get_shm_id() +{ + static char shm_id[128]; + sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId()); + return shm_id; +} + +static GstBusSyncReply tsmf_platform_bus_sync_handler(GstBus* bus, GstMessage* message, + gpointer user_data) +{ + struct X11Handle* hdl; + + TSMFGstreamerDecoder* decoder = user_data; + + if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT) + return GST_BUS_PASS; + +#if GST_VERSION_MAJOR > 0 + if (!gst_is_video_overlay_prepare_window_handle_message(message)) + return GST_BUS_PASS; +#else + if (!gst_structure_has_name(message->structure, "prepare-xwindow-id")) + return GST_BUS_PASS; +#endif + + hdl = (struct X11Handle*)decoder->platform; + + if (hdl->subwin) + { +#if GST_VERSION_MAJOR > 0 + hdl->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message)); + gst_video_overlay_set_window_handle(hdl->overlay, hdl->subwin); + gst_video_overlay_handle_events(hdl->overlay, FALSE); +#else + hdl->overlay = GST_X_OVERLAY(GST_MESSAGE_SRC(message)); +#if GST_CHECK_VERSION(0, 10, 31) + gst_x_overlay_set_window_handle(hdl->overlay, hdl->subwin); +#else + gst_x_overlay_set_xwindow_id(hdl->overlay, hdl->subwin); +#endif + gst_x_overlay_handle_events(hdl->overlay, TRUE); +#endif + + if (hdl->subwinWidth != -1 && hdl->subwinHeight != -1 && hdl->subwinX != -1 && + hdl->subwinY != -1) + { +#if GST_VERSION_MAJOR > 0 + if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth, + hdl->subwinHeight)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_video_overlay_expose(hdl->overlay); +#else + if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth, + hdl->subwinHeight)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_x_overlay_expose(hdl->overlay); +#endif + XLockDisplay(hdl->disp); + XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth, + hdl->subwinHeight); + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + } + else + { + g_warning("Window was not available before retrieving the overlay!"); + } + + gst_message_unref(message); + + return GST_BUS_DROP; +} + +const char* tsmf_platform_get_video_sink(void) +{ + return "autovideosink"; +} + +const char* tsmf_platform_get_audio_sink(void) +{ + return "autoaudiosink"; +} + +int tsmf_platform_create(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + if (decoder->platform) + return -1; + + hdl = calloc(1, sizeof(struct X11Handle)); + if (!hdl) + { + WLog_ERR(TAG, "Could not allocate handle."); + return -1; + } + + decoder->platform = hdl; + hdl->shmid = shm_open(get_shm_id(), (O_RDWR | O_CREAT), (PROT_READ | PROT_WRITE)); + if (hdl->shmid == -1) + { + char ebuffer[256] = { 0 }; + WLog_ERR(TAG, "failed to get access to shared memory - shmget(%s): %i - %s", get_shm_id(), + errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + return -2; + } + + hdl->xfwin = mmap(0, sizeof(void*), PROT_READ | PROT_WRITE, MAP_SHARED, hdl->shmid, 0); + if (hdl->xfwin == MAP_FAILED) + { + WLog_ERR(TAG, "shmat failed!"); + return -3; + } + + hdl->disp = XOpenDisplay(NULL); + if (!hdl->disp) + { + WLog_ERR(TAG, "Failed to open display"); + return -4; + } + + hdl->subwinMapped = FALSE; + hdl->subwinX = -1; + hdl->subwinY = -1; + hdl->subwinWidth = -1; + hdl->subwinHeight = -1; + + return 0; +} + +int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder) +{ + if (!decoder) + return -1; + + if (decoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + } + + return 0; +} + +int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder) +{ + GstBus* bus; + + if (!decoder) + return -1; + + if (!decoder->pipe) + return -1; + + bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipe)); + +#if GST_VERSION_MAJOR > 0 + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder, NULL); +#else + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder); +#endif + + if (!bus) + { + WLog_ERR(TAG, "gst_pipeline_get_bus failed!"); + return 1; + } + + gst_object_unref(bus); + + return 0; +} + +int tsmf_platform_free(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl = decoder->platform; + + if (!hdl) + return -1; + + if (hdl->disp) + XCloseDisplay(hdl->disp); + + if (hdl->xfwin) + munmap(0, sizeof(void*)); + + if (hdl->shmid >= 0) + close(hdl->shmid); + + free(hdl); + decoder->platform = NULL; + + return 0; +} + +int tsmf_window_create(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + { + decoder->ready = TRUE; + return -3; + } + else + { + if (!decoder) + return -1; + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + if (!hdl->subwin) + { + XLockDisplay(hdl->disp); + hdl->subwin = XCreateSimpleWindow(hdl->disp, *(int*)hdl->xfwin, 0, 0, 1, 1, 0, 0, 0); + XUnlockDisplay(hdl->disp); + + if (!hdl->subwin) + { + WLog_ERR(TAG, "Could not create subwindow!"); + } + } + + tsmf_window_map(decoder); + + decoder->ready = TRUE; +#if defined(WITH_XEXT) + int event, error; + XLockDisplay(hdl->disp); + hdl->has_shape = XShapeQueryExtension(hdl->disp, &event, &error); + XUnlockDisplay(hdl->disp); +#endif + } + + return 0; +} + +int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height, + int nr_rects, RDP_RECT* rects) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + { + return -3; + } + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + DEBUG_TSMF("resize: x=%d, y=%d, w=%d, h=%d", x, y, width, height); + + if (hdl->overlay) + { +#if GST_VERSION_MAJOR > 0 + + if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_video_overlay_expose(hdl->overlay); +#else + if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_x_overlay_expose(hdl->overlay); +#endif + } + + if (hdl->subwin) + { + hdl->subwinX = x; + hdl->subwinY = y; + hdl->subwinWidth = width; + hdl->subwinHeight = height; + + XLockDisplay(hdl->disp); + XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth, + hdl->subwinHeight); + + /* Unmap the window if there are no visibility rects */ + if (nr_rects == 0) + tsmf_window_unmap(decoder); + else + tsmf_window_map(decoder); + +#if defined(WITH_XEXT) + if (hdl->has_shape) + { + XRectangle* xrects = NULL; + + if (nr_rects == 0) + { + xrects = calloc(1, sizeof(XRectangle)); + xrects->x = x; + xrects->y = y; + xrects->width = width; + xrects->height = height; + } + else + { + xrects = calloc(nr_rects, sizeof(XRectangle)); + } + + if (xrects) + { + for (int i = 0; i < nr_rects; i++) + { + xrects[i].x = rects[i].x - x; + xrects[i].y = rects[i].y - y; + xrects[i].width = rects[i].width; + xrects[i].height = rects[i].height; + } + + XShapeCombineRectangles(hdl->disp, hdl->subwin, ShapeBounding, x, y, xrects, + nr_rects, ShapeSet, 0); + free(xrects); + } + } +#endif + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_map(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + if (!decoder) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + /* Only need to map the window if it is not currently mapped */ + if ((hdl->subwin) && (!hdl->subwinMapped)) + { + XLockDisplay(hdl->disp); + XMapWindow(hdl->disp, hdl->subwin); + hdl->subwinMapped = TRUE; + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_unmap(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + if (!decoder) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + /* only need to unmap window if it is currently mapped */ + if ((hdl->subwin) && (hdl->subwinMapped)) + { + XLockDisplay(hdl->disp); + XUnmapWindow(hdl->disp, hdl->subwin); + hdl->subwinMapped = FALSE; + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_destroy(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + decoder->ready = FALSE; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + return -3; + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + if (hdl->subwin) + { + XLockDisplay(hdl->disp); + XDestroyWindow(hdl->disp, hdl->subwin); + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + hdl->overlay = NULL; + hdl->subwin = 0; + hdl->subwinMapped = FALSE; + hdl->subwinX = -1; + hdl->subwinY = -1; + hdl->subwinWidth = -1; + hdl->subwinHeight = -1; + return 0; +} diff --git a/channels/tsmf/client/gstreamer/tsmf_gstreamer.c b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c new file mode 100644 index 0000000..9087876 --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c @@ -0,0 +1,1054 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder + * + * (C) Copyright 2012 HP Development Company, LLC + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak <armin.novak@thincast.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. + */ + +#include <freerdp/config.h> + +#include <winpr/assert.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <winpr/string.h> +#include <winpr/platform.h> + +#include <gst/gst.h> + +#include <gst/app/gstappsrc.h> +#include <gst/app/gstappsink.h> + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" +#include "tsmf_platform.h" + +/* 1 second = 10,000,000 100ns units*/ +#define SEEK_TOLERANCE 10 * 1000 * 1000 + +static BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder); +static void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder); +static int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, + GstState desired_state); +static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder); + +static const char* get_type(TSMFGstreamerDecoder* mdecoder) +{ + if (!mdecoder) + return NULL; + + switch (mdecoder->media_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + return "VIDEO"; + case TSMF_MAJOR_TYPE_AUDIO: + return "AUDIO"; + default: + return "UNKNOWN"; + } +} + +static void cb_child_added(GstChildProxy* child_proxy, GObject* object, + TSMFGstreamerDecoder* mdecoder) +{ + DEBUG_TSMF("NAME: %s", G_OBJECT_TYPE_NAME(object)); + + if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXvImageSink") || + !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXImageSink") || + !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstFluVAAutoSink")) + { + gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */ + g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */ + } + + else if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstAlsaSink") || + !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink")) + { + gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(object), "slave-method", 1, NULL); + g_object_set(G_OBJECT(object), "buffer-time", (gint64)20000, NULL); /* microseconds */ + g_object_set(G_OBJECT(object), "drift-tolerance", (gint64)20000, NULL); /* microseconds */ + g_object_set(G_OBJECT(object), "latency-time", (gint64)10000, NULL); /* microseconds */ + g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */ + g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */ + } +} + +static void tsmf_gstreamer_enough_data(GstAppSrc* src, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void)mdecoder; + DEBUG_TSMF("%s", get_type(mdecoder)); +} + +static void tsmf_gstreamer_need_data(GstAppSrc* src, guint length, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void)mdecoder; + DEBUG_TSMF("%s length=%u", get_type(mdecoder), length); +} + +static gboolean tsmf_gstreamer_seek_data(GstAppSrc* src, guint64 offset, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void)mdecoder; + DEBUG_TSMF("%s offset=%" PRIu64 "", get_type(mdecoder), offset); + + return TRUE; +} + +static BOOL tsmf_gstreamer_change_volume(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder || !mdecoder->pipe) + return TRUE; + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + return TRUE; + + mdecoder->gstMuted = (BOOL)muted; + DEBUG_TSMF("mute=[%" PRId32 "]", mdecoder->gstMuted); + mdecoder->gstVolume = (double)newVolume / (double)10000; + DEBUG_TSMF("gst_new_vol=[%f]", mdecoder->gstVolume); + + if (!mdecoder->volume) + return TRUE; + + if (!G_IS_OBJECT(mdecoder->volume)) + return TRUE; + + g_object_set(mdecoder->volume, "mute", mdecoder->gstMuted, NULL); + g_object_set(mdecoder->volume, "volume", mdecoder->gstVolume, NULL); + + return TRUE; +} + +static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp) +{ + /* + * Convert Microsoft 100ns timestamps to Gstreamer 1ns units. + */ + return (GstClockTime)(ms_timestamp * 100); +} + +int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, GstState desired_state) +{ + GstStateChangeReturn state_change; + const char* name; + const char* sname = get_type(mdecoder); + + if (!mdecoder) + return 0; + + if (!mdecoder->pipe) + return 0; /* Just in case this is called during startup or shutdown when we don't expect it + */ + + if (desired_state == mdecoder->state) + return 0; /* Redundant request - Nothing to do */ + + name = gst_element_state_get_name(desired_state); /* For debug */ + DEBUG_TSMF("%s to %s", sname, name); + state_change = gst_element_set_state(mdecoder->pipe, desired_state); + + if (state_change == GST_STATE_CHANGE_FAILURE) + { + WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_FAILURE.", sname, name); + } + else if (state_change == GST_STATE_CHANGE_ASYNC) + { + WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_ASYNC.", sname, name); + mdecoder->state = desired_state; + } + else + { + mdecoder->state = desired_state; + } + + return 0; +} + +static GstBuffer* tsmf_get_buffer_from_data(const void* raw_data, gsize size) +{ + GstBuffer* buffer; + gpointer data; + + if (!raw_data) + return NULL; + + if (size < 1) + return NULL; + + data = g_malloc(size); + + if (!data) + { + WLog_ERR(TAG, "Could not allocate %" G_GSIZE_FORMAT " bytes of data.", size); + return NULL; + } + + CopyMemory(data, raw_data, size); + +#if GST_VERSION_MAJOR > 0 + buffer = gst_buffer_new_wrapped(data, size); +#else + buffer = gst_buffer_new(); + + if (!buffer) + { + WLog_ERR(TAG, "Could not create GstBuffer"); + free(data); + return NULL; + } + + GST_BUFFER_MALLOCDATA(buffer) = data; + GST_BUFFER_SIZE(buffer) = size; + GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer); +#endif + + return buffer; +} + +static BOOL tsmf_gstreamer_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder) + return FALSE; + + DEBUG_TSMF(""); + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = TSMF_MAJOR_TYPE_VIDEO; + break; + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = TSMF_MAJOR_TYPE_AUDIO; + break; + default: + return FALSE; + } + + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion", + G_TYPE_INT, 3, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WVC1", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'V', 'C', '1'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION, + 1, 1, NULL); + break; + case TSMF_SUB_TYPE_MP4S: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-divx", "divxversion", G_TYPE_INT, 5, "bitrate", G_TYPE_UINT, + media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP42", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, NULL); + break; + case TSMF_SUB_TYPE_MP42: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 42, "bitrate", G_TYPE_UINT, + media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP42", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, NULL); + break; + case TSMF_SUB_TYPE_MP43: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 43, "bitrate", G_TYPE_UINT, + media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP43", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '3'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, NULL); + break; + case TSMF_SUB_TYPE_M4S2: + mdecoder->gst_caps = gst_caps_new_simple( + "video/mpeg", "mpegversion", G_TYPE_INT, 4, "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "M4S2", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', '4', 'S', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, NULL); + break; + case TSMF_SUB_TYPE_WMA9: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-wma", "wmaversion", G_TYPE_INT, 3, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth", + G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT, + media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL); + break; + case TSMF_SUB_TYPE_WMA1: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-wma", "wmaversion", G_TYPE_INT, 1, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth", + G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT, + media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL); + break; + case TSMF_SUB_TYPE_WMA2: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-wma", "wmaversion", G_TYPE_INT, 2, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth", + G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT, + media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL); + break; + case TSMF_SUB_TYPE_MP3: + mdecoder->gst_caps = + gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, + 3, "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, NULL); + break; + case TSMF_SUB_TYPE_WMV1: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion", + G_TYPE_INT, 1, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV1", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '1'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, NULL); + break; + case TSMF_SUB_TYPE_WMV2: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, "wmvversion", G_TYPE_INT, 2, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV2", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION, + 1, 1, NULL); + break; + case TSMF_SUB_TYPE_WMV3: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion", + G_TYPE_INT, 3, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV3", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '3'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION, + 1, 1, NULL); + break; + case TSMF_SUB_TYPE_AVC1: + case TSMF_SUB_TYPE_H264: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-h264", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, "framerate", GST_TYPE_FRACTION, + media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, "stream-format", G_TYPE_STRING, + "byte-stream", "alignment", G_TYPE_STRING, "nal", NULL); + break; + case TSMF_SUB_TYPE_AC3: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-ac3", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, NULL); + break; + case TSMF_SUB_TYPE_AAC: + + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + if (media_type->ExtraDataSize < 12) + return FALSE; + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + + mdecoder->gst_caps = gst_caps_new_simple( + "audio/mpeg", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, "mpegversion", G_TYPE_INT, 4, + "framed", G_TYPE_BOOLEAN, TRUE, "stream-format", G_TYPE_STRING, "raw", NULL); + break; + case TSMF_SUB_TYPE_MP1A: + mdecoder->gst_caps = + gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "channels", + G_TYPE_INT, media_type->Channels, NULL); + break; + case TSMF_SUB_TYPE_MP1V: + mdecoder->gst_caps = + gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 1, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case TSMF_SUB_TYPE_YUY2: +#if GST_VERSION_MAJOR > 0 + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-raw", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, NULL); +#else + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-raw-yuv", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "framerate", + GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, NULL); +#endif + break; + case TSMF_SUB_TYPE_MP2V: + mdecoder->gst_caps = gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 2, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case TSMF_SUB_TYPE_MP2A: + mdecoder->gst_caps = + gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, NULL); + break; + case TSMF_SUB_TYPE_FLAC: + mdecoder->gst_caps = gst_caps_new_simple("audio/x-flac", "", NULL); + break; + default: + WLog_ERR(TAG, "unknown format:(%d).", media_type->SubType); + return FALSE; + } + + if (media_type->ExtraDataSize > 0) + { + GstBuffer* buffer; + DEBUG_TSMF("Extra data available (%" PRIu32 ")", media_type->ExtraDataSize); + buffer = tsmf_get_buffer_from_data(media_type->ExtraData, media_type->ExtraDataSize); + + if (!buffer) + { + WLog_ERR(TAG, "could not allocate GstBuffer!"); + return FALSE; + } + + gst_caps_set_simple(mdecoder->gst_caps, "codec_data", GST_TYPE_BUFFER, buffer, NULL); + } + + DEBUG_TSMF("%p format '%s'", (void*)mdecoder, gst_caps_to_string(mdecoder->gst_caps)); + tsmf_platform_set_format(mdecoder); + + /* Create the pipeline... */ + if (!tsmf_gstreamer_pipeline_build(mdecoder)) + return FALSE; + + return TRUE; +} + +void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder) +{ + if (!mdecoder || !mdecoder->pipe) + return; + + if (mdecoder->pipe && GST_OBJECT_REFCOUNT_VALUE(mdecoder->pipe) > 0) + { + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL); + gst_object_unref(mdecoder->pipe); + } + + mdecoder->ready = FALSE; + mdecoder->paused = FALSE; + + mdecoder->pipe = NULL; + mdecoder->src = NULL; + mdecoder->queue = NULL; +} + +BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder) +{ +#if GST_VERSION_MAJOR > 0 + const char* video = + "appsrc name=videosource ! queue2 name=videoqueue ! decodebin name=videodecoder !"; + const char* audio = + "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin name=audiodecoder ! " + "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !"; +#else + const char* video = + "appsrc name=videosource ! queue2 name=videoqueue ! decodebin2 name=videodecoder !"; + const char* audio = + "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin2 name=audiodecoder ! " + "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !"; +#endif + char pipeline[1024]; + + if (!mdecoder) + return FALSE; + + /* TODO: Construction of the pipeline from a string allows easy overwrite with arguments. + * The only fixed elements necessary are appsrc and the volume element for audio streams. + * The rest could easily be provided in gstreamer pipeline notation from command line. */ + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + sprintf_s(pipeline, sizeof(pipeline), "%s %s name=videosink", video, + tsmf_platform_get_video_sink()); + else + sprintf_s(pipeline, sizeof(pipeline), "%s %s name=audiosink", audio, + tsmf_platform_get_audio_sink()); + + DEBUG_TSMF("pipeline=%s", pipeline); + mdecoder->pipe = gst_parse_launch(pipeline, NULL); + + if (!mdecoder->pipe) + { + WLog_ERR(TAG, "Failed to create new pipe"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosource"); + else + mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosource"); + + if (!mdecoder->src) + { + WLog_ERR(TAG, "Failed to get appsrc"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videoqueue"); + else + mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audioqueue"); + + if (!mdecoder->queue) + { + WLog_ERR(TAG, "Failed to get queue"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosink"); + else + mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosink"); + + if (!mdecoder->outsink) + { + WLog_ERR(TAG, "Failed to get sink"); + return FALSE; + } + + g_signal_connect(mdecoder->outsink, "child-added", G_CALLBACK(cb_child_added), mdecoder); + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_AUDIO) + { + mdecoder->volume = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiovolume"); + + if (!mdecoder->volume) + { + WLog_ERR(TAG, "Failed to get volume"); + return FALSE; + } + + tsmf_gstreamer_change_volume((ITSMFDecoder*)mdecoder, mdecoder->gstVolume * ((double)10000), + mdecoder->gstMuted); + } + + tsmf_platform_register_handler(mdecoder); + /* AppSrc settings */ + GstAppSrcCallbacks callbacks = { + tsmf_gstreamer_need_data, tsmf_gstreamer_enough_data, tsmf_gstreamer_seek_data, { NULL } + }; + g_object_set(mdecoder->src, "format", GST_FORMAT_TIME, NULL); + g_object_set(mdecoder->src, "is-live", FALSE, NULL); + g_object_set(mdecoder->src, "block", FALSE, NULL); + g_object_set(mdecoder->src, "blocksize", 1024, NULL); + gst_app_src_set_caps((GstAppSrc*)mdecoder->src, mdecoder->gst_caps); + gst_app_src_set_callbacks((GstAppSrc*)mdecoder->src, &callbacks, mdecoder, NULL); + gst_app_src_set_stream_type((GstAppSrc*)mdecoder->src, GST_APP_STREAM_TYPE_SEEKABLE); + gst_app_src_set_latency((GstAppSrc*)mdecoder->src, 0, -1); + gst_app_src_set_max_bytes((GstAppSrc*)mdecoder->src, (guint64)0); // unlimited + g_object_set(G_OBJECT(mdecoder->queue), "use-buffering", FALSE, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "use-rate-estimate", FALSE, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-buffers", 0, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-bytes", 0, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-time", (guint64)0, NULL); + + /* Only set these properties if not an autosink, otherwise we will set properties when real + * sinks are added */ + if (!g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoVideoSink") && + !g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoAudioSink")) + { + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink, + 10000000); /* nanoseconds */ + } + else + { + gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink, + 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "buffer-time", (gint64)20000, + NULL); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "drift-tolerance", (gint64)20000, + NULL); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "latency-time", (gint64)10000, + NULL); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "slave-method", 1, NULL); + } + g_object_set(G_OBJECT(mdecoder->outsink), "sync", TRUE, + NULL); /* synchronize on the clock */ + g_object_set(G_OBJECT(mdecoder->outsink), "async", TRUE, NULL); /* no async state changes */ + } + + tsmf_window_create(mdecoder); + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_READY); + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + mdecoder->pipeline_start_time_valid = 0; + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(mdecoder->pipe), GST_DEBUG_GRAPH_SHOW_ALL, + get_type(mdecoder)); + + return TRUE; +} + +static BOOL tsmf_gstreamer_decodeEx(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions, UINT64 start_time, UINT64 end_time, + UINT64 duration) +{ + GstBuffer* gst_buf; + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + UINT64 sample_time = tsmf_gstreamer_timestamp_ms_to_gst(start_time); + BOOL useTimestamps = TRUE; + + if (!mdecoder) + { + WLog_ERR(TAG, "Decoder not initialized!"); + return FALSE; + } + + /* + * This function is always called from a stream-specific thread. + * It should be alright to block here if necessary. + * We don't expect to block here often, since the pipeline should + * have more than enough buffering. + */ + DEBUG_TSMF( + "%s. Start:(%" PRIu64 ") End:(%" PRIu64 ") Duration:(%" PRIu64 ") Last Start:(%" PRIu64 ")", + get_type(mdecoder), start_time, end_time, duration, mdecoder->last_sample_start_time); + + if (mdecoder->shutdown) + { + WLog_ERR(TAG, "decodeEx called on shutdown decoder"); + return TRUE; + } + + if (mdecoder->gst_caps == NULL) + { + WLog_ERR(TAG, "tsmf_gstreamer_set_format not called or invalid format."); + return FALSE; + } + + if (!mdecoder->pipe) + tsmf_gstreamer_pipeline_build(mdecoder); + + if (!mdecoder->src) + { + WLog_ERR( + TAG, + "failed to construct pipeline correctly. Unable to push buffer to source element."); + return FALSE; + } + + gst_buf = tsmf_get_buffer_from_data(data, data_size); + + if (gst_buf == NULL) + { + WLog_ERR(TAG, "tsmf_get_buffer_from_data(%p, %" PRIu32 ") failed.", (void*)data, data_size); + return FALSE; + } + + /* Relative timestamping will sometimes be set to 0 + * so we ignore these timestamps just to be safe(bit 8) + */ + if (extensions & 0x00000080) + { + DEBUG_TSMF("Ignoring the timestamps - relative - bit 8"); + useTimestamps = FALSE; + } + + /* If no timestamps exist then we dont want to look at the timestamp values (bit 7) */ + if (extensions & 0x00000040) + { + DEBUG_TSMF("Ignoring the timestamps - none - bit 7"); + useTimestamps = FALSE; + } + + /* If performing a seek */ + if (mdecoder->seeking) + { + mdecoder->seeking = FALSE; + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED); + mdecoder->pipeline_start_time_valid = 0; + } + + if (mdecoder->pipeline_start_time_valid) + { + DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time); + + /* Adjusted the condition for a seek to be based on start time only + * WMV1 and WMV2 files in particular have bad end time and duration values + * there seems to be no real side effects of just using the start time instead + */ + UINT64 minTime = mdecoder->last_sample_start_time - (UINT64)SEEK_TOLERANCE; + UINT64 maxTime = mdecoder->last_sample_start_time + (UINT64)SEEK_TOLERANCE; + + /* Make sure the minTime stops at 0 , should we be at the beginning of the stream */ + if (mdecoder->last_sample_start_time < (UINT64)SEEK_TOLERANCE) + minTime = 0; + + /* If the start_time is valid and different from the previous start time by more than the + * seek tolerance, then we have a seek condition */ + if (((start_time > maxTime) || (start_time < minTime)) && useTimestamps) + { + DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64 + "] > last_sample_start_time=[%" PRIu64 "] OR ", + start_time, mdecoder->last_sample_start_time); + DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64 + "] < last_sample_start_time=[%" PRIu64 "] with", + start_time, mdecoder->last_sample_start_time); + DEBUG_TSMF( + "tsmf_gstreamer_decodeEX: a tolerance of more than [%lu] from the last sample", + SEEK_TOLERANCE); + DEBUG_TSMF("tsmf_gstreamer_decodeEX: minTime=[%" PRIu64 "] maxTime=[%" PRIu64 "]", + minTime, maxTime); + + mdecoder->seeking = TRUE; + + /* since we cant make the gstreamer pipeline jump to the new start time after a seek - + * we just maintain a offset between realtime and gstreamer time + */ + mdecoder->seek_offset = start_time; + } + } + else + { + DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time); + /* Always set base/start time to 0. Will use seek offset to translate real buffer times + * back to 0. This allows the video to be started from anywhere and the ability to handle + * seeks without rebuilding the pipeline, etc. since that is costly + */ + gst_element_set_base_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0)); + gst_element_set_start_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0)); + mdecoder->pipeline_start_time_valid = 1; + + /* Set the seek offset if buffer has valid timestamps. */ + if (useTimestamps) + mdecoder->seek_offset = start_time; + + if (!gst_element_seek(mdecoder->pipe, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + { + WLog_ERR(TAG, "seek failed"); + } + } + +#if GST_VERSION_MAJOR > 0 + if (useTimestamps) + GST_BUFFER_PTS(gst_buf) = + sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset); + else + GST_BUFFER_PTS(gst_buf) = GST_CLOCK_TIME_NONE; +#else + if (useTimestamps) + GST_BUFFER_TIMESTAMP(gst_buf) = + sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset); + else + GST_BUFFER_TIMESTAMP(gst_buf) = GST_CLOCK_TIME_NONE; +#endif + GST_BUFFER_DURATION(gst_buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET(gst_buf) = GST_BUFFER_OFFSET_NONE; +#if GST_VERSION_MAJOR > 0 +#else + gst_buffer_set_caps(gst_buf, mdecoder->gst_caps); +#endif + gst_app_src_push_buffer(GST_APP_SRC(mdecoder->src), gst_buf); + + /* Should only update the last timestamps if the current ones are valid */ + if (useTimestamps) + { + mdecoder->last_sample_start_time = start_time; + mdecoder->last_sample_end_time = end_time; + } + + if (mdecoder->pipe && (GST_STATE(mdecoder->pipe) != GST_STATE_PLAYING)) + { + DEBUG_TSMF("%s: state=%s", get_type(mdecoder), + gst_element_state_get_name(GST_STATE(mdecoder->pipe))); + + DEBUG_TSMF("%s Paused: %" PRIi32 " Shutdown: %i Ready: %" PRIi32 "", get_type(mdecoder), + mdecoder->paused, mdecoder->shutdown, mdecoder->ready); + if (!mdecoder->paused && !mdecoder->shutdown && mdecoder->ready) + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + } + + return TRUE; +} + +static BOOL tsmf_gstreamer_control(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder) + { + WLog_ERR(TAG, "Control called with no decoder!"); + return TRUE; + } + + if (control_msg == Control_Pause) + { + DEBUG_TSMF("Control_Pause %s", get_type(mdecoder)); + + if (mdecoder->paused) + { + WLog_ERR(TAG, "%s: Ignoring Control_Pause, already received!", get_type(mdecoder)); + return TRUE; + } + + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED); + mdecoder->shutdown = 0; + mdecoder->paused = TRUE; + } + else if (control_msg == Control_Resume) + { + DEBUG_TSMF("Control_Resume %s", get_type(mdecoder)); + + if (!mdecoder->paused && !mdecoder->shutdown) + { + WLog_ERR(TAG, "%s: Ignoring Control_Resume, already received!", get_type(mdecoder)); + return TRUE; + } + + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + } + else if (control_msg == Control_Stop) + { + DEBUG_TSMF("Control_Stop %s", get_type(mdecoder)); + + if (mdecoder->shutdown) + { + WLog_ERR(TAG, "%s: Ignoring Control_Stop, already received!", get_type(mdecoder)); + return TRUE; + } + + /* Reset stamps, flush buffers, etc */ + if (mdecoder->pipe) + { + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL); + tsmf_window_destroy(mdecoder); + tsmf_gstreamer_clean_up(mdecoder); + } + mdecoder->seek_offset = 0; + mdecoder->pipeline_start_time_valid = 0; + mdecoder->shutdown = 1; + } + else if (control_msg == Control_Restart) + { + DEBUG_TSMF("Control_Restart %s", get_type(mdecoder)); + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + + if (mdecoder->pipeline_start_time_valid) + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + } + else + WLog_ERR(TAG, "Unknown control message %08x", control_msg); + + return TRUE; +} + +static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF(""); + + if (!mdecoder) + return FALSE; + + guint clbuff = 0; + + if (G_IS_OBJECT(mdecoder->queue)) + g_object_get(mdecoder->queue, "current-level-buffers", &clbuff, NULL); + + DEBUG_TSMF("%s buffer level %u", get_type(mdecoder), clbuff); + return clbuff; +} + +static void tsmf_gstreamer_free(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF("%s", get_type(mdecoder)); + + if (mdecoder) + { + tsmf_window_destroy(mdecoder); + tsmf_gstreamer_clean_up(mdecoder); + + if (mdecoder->gst_caps) + gst_caps_unref(mdecoder->gst_caps); + + tsmf_platform_free(mdecoder); + ZeroMemory(mdecoder, sizeof(TSMFGstreamerDecoder)); + free(mdecoder); + mdecoder = NULL; + } +} + +static UINT64 tsmf_gstreamer_get_running_time(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder) + return 0; + + if (!mdecoder->outsink) + return mdecoder->last_sample_start_time; + + if (!mdecoder->pipe) + return 0; + + GstFormat fmt = GST_FORMAT_TIME; + gint64 pos = 0; +#if GST_VERSION_MAJOR > 0 + gst_element_query_position(mdecoder->pipe, fmt, &pos); +#else + gst_element_query_position(mdecoder->pipe, &fmt, &pos); +#endif + return (UINT64)(pos / 100 + mdecoder->seek_offset); +} + +static BOOL tsmf_gstreamer_update_rendering_area(ITSMFDecoder* decoder, int newX, int newY, + int newWidth, int newHeight, int numRectangles, + RDP_RECT* rectangles) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF("x=%d, y=%d, w=%d, h=%d, rect=%d", newX, newY, newWidth, newHeight, numRectangles); + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + return tsmf_window_resize(mdecoder, newX, newY, newWidth, newHeight, numRectangles, + rectangles) == 0; + } + + return TRUE; +} + +static BOOL tsmf_gstreamer_ack(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF(""); + mdecoder->ack_cb = NULL; + mdecoder->stream = stream; + return TRUE; +} + +static BOOL tsmf_gstreamer_sync(ITSMFDecoder* decoder, void (*cb)(void*), void* stream) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF(""); + mdecoder->sync_cb = NULL; + mdecoder->stream = stream; + return TRUE; +} + +FREERDP_ENTRY_POINT(ITSMFDecoder* gstreamer_freerdp_tsmf_client_decoder_subsystem_entry(void)) +{ + TSMFGstreamerDecoder* decoder; + +#if GST_CHECK_VERSION(0, 10, 31) + if (!gst_is_initialized()) + { + gst_init(NULL, NULL); + } +#else + gst_init(NULL, NULL); +#endif + + decoder = calloc(1, sizeof(TSMFGstreamerDecoder)); + + if (!decoder) + return NULL; + + decoder->iface.SetFormat = tsmf_gstreamer_set_format; + decoder->iface.Decode = NULL; + decoder->iface.GetDecodedData = NULL; + decoder->iface.GetDecodedFormat = NULL; + decoder->iface.GetDecodedDimension = NULL; + decoder->iface.GetRunningTime = tsmf_gstreamer_get_running_time; + decoder->iface.UpdateRenderingArea = tsmf_gstreamer_update_rendering_area; + decoder->iface.Free = tsmf_gstreamer_free; + decoder->iface.Control = tsmf_gstreamer_control; + decoder->iface.DecodeEx = tsmf_gstreamer_decodeEx; + decoder->iface.ChangeVolume = tsmf_gstreamer_change_volume; + decoder->iface.BufferLevel = tsmf_gstreamer_buffer_level; + decoder->iface.SetAckFunc = tsmf_gstreamer_ack; + decoder->iface.SetSyncFunc = tsmf_gstreamer_sync; + decoder->paused = FALSE; + decoder->gstVolume = 0.5; + decoder->gstMuted = FALSE; + decoder->state = GST_STATE_VOID_PENDING; /* No real state yet */ + decoder->last_sample_start_time = 0; + decoder->last_sample_end_time = 0; + decoder->seek_offset = 0; + decoder->seeking = FALSE; + + if (tsmf_platform_create(decoder) < 0) + { + free(decoder); + return NULL; + } + + return (ITSMFDecoder*)decoder; +} diff --git a/channels/tsmf/client/gstreamer/tsmf_platform.h b/channels/tsmf/client/gstreamer/tsmf_platform.h new file mode 100644 index 0000000..681095c --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_platform.h @@ -0,0 +1,85 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder + * platform specific functions + * + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak <armin.novak@thincast.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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H +#define FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H + +#include <gst/gst.h> +#include <tsmf_decoder.h> + +typedef struct +{ + ITSMFDecoder iface; + + int media_type; /* TSMF_MAJOR_TYPE_AUDIO or TSMF_MAJOR_TYPE_VIDEO */ + + gint64 duration; + + GstState state; + GstCaps* gst_caps; + + GstElement* pipe; + GstElement* src; + GstElement* queue; + GstElement* outsink; + GstElement* volume; + + BOOL ready; + BOOL paused; + UINT64 last_sample_start_time; + UINT64 last_sample_end_time; + BOOL seeking; + UINT64 seek_offset; + + double gstVolume; + BOOL gstMuted; + + int pipeline_start_time_valid; /* We've set the start time and have not reset the pipeline */ + int shutdown; /* The decoder stream is shutting down */ + + void* platform; + + BOOL (*ack_cb)(void*, BOOL); + void (*sync_cb)(void*); + void* stream; + +} TSMFGstreamerDecoder; + +const char* tsmf_platform_get_video_sink(void); +const char* tsmf_platform_get_audio_sink(void); + +int tsmf_platform_create(TSMFGstreamerDecoder* decoder); +int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder); +int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder); +int tsmf_platform_free(TSMFGstreamerDecoder* decoder); + +int tsmf_window_create(TSMFGstreamerDecoder* decoder); +int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height, + int nr_rect, RDP_RECT* visible); +int tsmf_window_destroy(TSMFGstreamerDecoder* decoder); + +int tsmf_window_map(TSMFGstreamerDecoder* decoder); +int tsmf_window_unmap(TSMFGstreamerDecoder* decoder); + +BOOL tsmf_gstreamer_add_pad(TSMFGstreamerDecoder* mdecoder); +void tsmf_gstreamer_remove_pad(TSMFGstreamerDecoder* mdecoder); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H */ diff --git a/channels/tsmf/client/oss/CMakeLists.txt b/channels/tsmf/client/oss/CMakeLists.txt new file mode 100644 index 0000000..9ff9e81 --- /dev/null +++ b/channels/tsmf/client/oss/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@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. + +define_channel_client_subsystem("tsmf" "oss" "audio") + +find_package(OSS REQUIRED) + +set(${MODULE_PREFIX}_SRCS + tsmf_oss.c +) + +set(${MODULE_PREFIX}_LIBS + winpr + ${OSS_LIBRARIES} +) + +include_directories(..) +include_directories(${OSS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + diff --git a/channels/tsmf/client/oss/tsmf_oss.c b/channels/tsmf/client/oss/tsmf_oss.c new file mode 100644 index 0000000..666b419 --- /dev/null +++ b/channels/tsmf/client/oss/tsmf_oss.c @@ -0,0 +1,248 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - OSS Audio Device + * + * Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <winpr/crt.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <unistd.h> +#if defined(__OpenBSD__) +#include <soundcard.h> +#else +#include <sys/soundcard.h> +#endif +#include <sys/ioctl.h> + +#include <freerdp/types.h> +#include <freerdp/codec/dsp.h> + +#include "tsmf_audio.h" + +typedef struct +{ + ITSMFAudioDevice iface; + + char dev_name[PATH_MAX]; + int pcm_handle; + + UINT32 sample_rate; + UINT32 channels; + UINT32 bits_per_sample; + + UINT32 data_size_last; +} TSMFOssAudioDevice; + +#define OSS_LOG_ERR(_text, _error) \ + do \ + { \ + if ((_error) != 0) \ + { \ + char ebuffer[256] = { 0 }; \ + WLog_ERR(TAG, "%s: %i - %s", (_text), (_error), \ + winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \ + } \ + } while (0) + +static BOOL tsmf_oss_open(ITSMFAudioDevice* audio, const char* device) +{ + int tmp = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL || oss->pcm_handle != -1) + return FALSE; + + if (device == NULL) /* Default device. */ + { + strncpy(oss->dev_name, "/dev/dsp", sizeof(oss->dev_name)); + } + else + { + strncpy(oss->dev_name, device, sizeof(oss->dev_name) - 1); + } + + if ((oss->pcm_handle = open(oss->dev_name, O_WRONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + oss->pcm_handle = -1; + return FALSE; + } + +#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */ + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno); + } + else if ((mask & PCM_CAP_OUTPUT) == 0) + { + OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + +#endif + const int rc = ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &tmp); + if (rc == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + if ((AFMT_S16_LE & tmp) == 0) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS - AFMT_S16_LE", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + WLog_INFO(TAG, "open: %s", oss->dev_name); + return TRUE; +} + +static BOOL tsmf_oss_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample) +{ + int tmp = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL || oss->pcm_handle == -1) + return FALSE; + + oss->sample_rate = sample_rate; + oss->channels = channels; + oss->bits_per_sample = bits_per_sample; + tmp = AFMT_S16_LE; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + + tmp = channels; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + + tmp = sample_rate; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + + tmp = ((bits_per_sample / 8) * channels * sample_rate); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + + DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "", + sample_rate, channels, bits_per_sample); + return TRUE; +} + +static BOOL tsmf_oss_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size) +{ + int status = 0; + UINT32 offset = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + DEBUG_TSMF("tsmf_oss_play: data_size %" PRIu32 "", data_size); + + if (oss == NULL || oss->pcm_handle == -1) + return FALSE; + + if (data == NULL || data_size == 0) + return TRUE; + + offset = 0; + oss->data_size_last = data_size; + + while (offset < data_size) + { + status = write(oss->pcm_handle, &data[offset], (data_size - offset)); + + if (status < 0) + { + OSS_LOG_ERR("write fail", errno); + return FALSE; + } + + offset += status; + } + + return TRUE; +} + +static UINT64 tsmf_oss_get_latency(ITSMFAudioDevice* audio) +{ + UINT64 latency = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL) + return 0; + + // latency = ((oss->data_size_last / (oss->bits_per_sample / 8)) * oss->sample_rate); + // WLog_INFO(TAG, "latency: %zu", latency); + return latency; +} + +static BOOL tsmf_oss_flush(ITSMFAudioDevice* audio) +{ + return TRUE; +} + +static void tsmf_oss_free(ITSMFAudioDevice* audio) +{ + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL) + return; + + if (oss->pcm_handle != -1) + { + WLog_INFO(TAG, "close: %s", oss->dev_name); + close(oss->pcm_handle); + } + + free(oss); +} + +FREERDP_ENTRY_POINT(ITSMFAudioDevice* oss_freerdp_tsmf_client_audio_subsystem_entry(void)) +{ + TSMFOssAudioDevice* oss = calloc(1, sizeof(TSMFOssAudioDevice)); + if (!oss) + return NULL; + + oss->iface.Open = tsmf_oss_open; + oss->iface.SetFormat = tsmf_oss_set_format; + oss->iface.Play = tsmf_oss_play; + oss->iface.GetLatency = tsmf_oss_get_latency; + oss->iface.Flush = tsmf_oss_flush; + oss->iface.Free = tsmf_oss_free; + oss->pcm_handle = -1; + return &oss->iface; +} diff --git a/channels/tsmf/client/pulse/CMakeLists.txt b/channels/tsmf/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..1aa67f0 --- /dev/null +++ b/channels/tsmf/client/pulse/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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. + +define_channel_client_subsystem("tsmf" "pulse" "audio") + +find_package(PulseAudio REQUIRED) + +set(${MODULE_PREFIX}_SRCS + tsmf_pulse.c +) + +set(${MODULE_PREFIX}_LIBS + winpr + ${PULSEAUDIO_LIBRARY} + ${PULSEAUDIO_MAINLOOP_LIBRARY} +) + +include_directories(..) +include_directories(${PULSEAUDIO_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/channels/tsmf/client/pulse/tsmf_pulse.c b/channels/tsmf/client/pulse/tsmf_pulse.c new file mode 100644 index 0000000..6d3cb1c --- /dev/null +++ b/channels/tsmf/client/pulse/tsmf_pulse.c @@ -0,0 +1,414 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - PulseAudio Device + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <winpr/crt.h> + +#include <pulse/pulseaudio.h> + +#include "tsmf_audio.h" + +typedef struct +{ + ITSMFAudioDevice iface; + + char device[32]; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; +} TSMFPulseAudioDevice; + +static void tsmf_pulse_context_state_callback(pa_context* context, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + pa_context_state_t state = pa_context_get_state(context); + + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_TSMF("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_TSMF("state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_TSMF("state %d", state); + break; + } +} + +static BOOL tsmf_pulse_connect(TSMFPulseAudioDevice* pulse) +{ + pa_context_state_t state = PA_CONTEXT_FAILED; + + if (!pulse->context) + return FALSE; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + WLog_ERR(TAG, "pa_context_connect failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_threaded_mainloop_start failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + DEBUG_TSMF("bad context state (%d)", pa_context_errno(pulse->context)); + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_CONTEXT_READY) + { + DEBUG_TSMF("connected"); + return TRUE; + } + else + { + pa_context_disconnect(pulse->context); + return FALSE; + } +} + +static BOOL tsmf_pulse_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + + if (device) + { + strncpy(pulse->device, device, sizeof(pulse->device) - 1); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + { + WLog_ERR(TAG, "pa_threaded_mainloop_new failed"); + return FALSE; + } + + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + + if (!pulse->context) + { + WLog_ERR(TAG, "pa_context_new failed"); + return FALSE; + } + + pa_context_set_state_callback(pulse->context, tsmf_pulse_context_state_callback, pulse); + + if (!tsmf_pulse_connect(pulse)) + { + WLog_ERR(TAG, "tsmf_pulse_connect failed"); + return FALSE; + } + + DEBUG_TSMF("open device %s", pulse->device); + return TRUE; +} + +static void tsmf_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void tsmf_pulse_wait_for_operation(TSMFPulseAudioDevice* pulse, pa_operation* operation) +{ + if (operation == NULL) + return; + + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_operation_unref(operation); +} + +static void tsmf_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + pa_stream_state_t state = pa_stream_get_state(stream); + + switch (state) + { + case PA_STREAM_READY: + DEBUG_TSMF("PA_STREAM_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_TSMF("state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_TSMF("state %d", state); + break; + } +} + +static void tsmf_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + DEBUG_TSMF("%" PRIdz "", length); + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static BOOL tsmf_pulse_close_stream(TSMFPulseAudioDevice* pulse) +{ + if (!pulse->context || !pulse->stream) + return FALSE; + + DEBUG_TSMF(""); + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_set_write_callback(pulse->stream, NULL, NULL); + tsmf_pulse_wait_for_operation( + pulse, pa_stream_drain(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static BOOL tsmf_pulse_open_stream(TSMFPulseAudioDevice* pulse) +{ + pa_stream_state_t state = PA_STREAM_FAILED; + pa_buffer_attr buffer_attr = { 0 }; + + if (!pulse->context) + return FALSE; + + DEBUG_TSMF(""); + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp", &pulse->sample_spec, NULL); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_stream_new failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + pa_stream_set_state_callback(pulse->stream, tsmf_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, tsmf_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = pa_usec_to_bytes(500000, &pulse->sample_spec); + buffer_attr.tlength = pa_usec_to_bytes(250000, &pulse->sample_spec); + buffer_attr.prebuf = (UINT32)-1; + buffer_attr.minreq = (UINT32)-1; + buffer_attr.fragsize = (UINT32)-1; + + if (pa_stream_connect_playback( + pulse->stream, pulse->device[0] ? pulse->device : NULL, &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, + NULL, NULL) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_stream_connect_playback failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + WLog_ERR(TAG, "bad stream state (%d)", pa_context_errno(pulse->context)); + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_STREAM_READY) + { + DEBUG_TSMF("connected"); + return TRUE; + } + else + { + tsmf_pulse_close_stream(pulse); + return FALSE; + } +} + +static BOOL tsmf_pulse_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "", + sample_rate, channels, bits_per_sample); + pulse->sample_spec.rate = sample_rate; + pulse->sample_spec.channels = channels; + pulse->sample_spec.format = PA_SAMPLE_S16LE; + return tsmf_pulse_open_stream(pulse); +} + +static BOOL tsmf_pulse_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + const BYTE* src = NULL; + size_t len = 0; + int ret = 0; + DEBUG_TSMF("data_size %" PRIu32 "", data_size); + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + src = data; + + while (data_size > 0) + { + while ((len = pa_stream_writable_size(pulse->stream)) == 0) + { + DEBUG_TSMF("waiting"); + pa_threaded_mainloop_wait(pulse->mainloop); + } + + if (len == (size_t)-1) + break; + + if (len > data_size) + len = data_size; + + ret = pa_stream_write(pulse->stream, src, len, NULL, 0LL, PA_SEEK_RELATIVE); + + if (ret < 0) + { + DEBUG_TSMF("pa_stream_write failed (%d)", pa_context_errno(pulse->context)); + break; + } + + src += len; + data_size -= len; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + } + + return TRUE; +} + +static UINT64 tsmf_pulse_get_latency(ITSMFAudioDevice* audio) +{ + pa_usec_t usec = 0; + UINT64 latency = 0; + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + + if (pulse->stream && pa_stream_get_latency(pulse->stream, &usec, NULL) == 0) + { + latency = ((UINT64)usec) * 10LL; + } + + return latency; +} + +static BOOL tsmf_pulse_flush(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + pa_threaded_mainloop_lock(pulse->mainloop); + tsmf_pulse_wait_for_operation( + pulse, pa_stream_flush(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static void tsmf_pulse_free(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + DEBUG_TSMF(""); + tsmf_pulse_close_stream(pulse); + + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + + free(pulse); +} + +FREERDP_ENTRY_POINT(ITSMFAudioDevice* pulse_freerdp_tsmf_client_audio_subsystem_entry(void)) +{ + TSMFPulseAudioDevice* pulse = NULL; + pulse = (TSMFPulseAudioDevice*)calloc(1, sizeof(TSMFPulseAudioDevice)); + + if (!pulse) + return NULL; + + pulse->iface.Open = tsmf_pulse_open; + pulse->iface.SetFormat = tsmf_pulse_set_format; + pulse->iface.Play = tsmf_pulse_play; + pulse->iface.GetLatency = tsmf_pulse_get_latency; + pulse->iface.Flush = tsmf_pulse_flush; + pulse->iface.Free = tsmf_pulse_free; + return (ITSMFAudioDevice*)pulse; +} diff --git a/channels/tsmf/client/tsmf_audio.c b/channels/tsmf/client/tsmf_audio.c new file mode 100644 index 0000000..89202ef --- /dev/null +++ b/channels/tsmf/client/tsmf_audio.c @@ -0,0 +1,97 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Audio Device Manager + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "tsmf_audio.h" + +static ITSMFAudioDevice* tsmf_load_audio_device_by_name(const char* name, const char* device) +{ + ITSMFAudioDevice* audio = NULL; + TSMF_AUDIO_DEVICE_ENTRY entry = NULL; + + entry = + (TSMF_AUDIO_DEVICE_ENTRY)(void*)freerdp_load_channel_addin_entry("tsmf", name, "audio", 0); + + if (!entry) + return NULL; + + audio = entry(); + + if (!audio) + { + WLog_ERR(TAG, "failed to call export function in %s", name); + return NULL; + } + + if (!audio->Open(audio, device)) + { + audio->Free(audio); + audio = NULL; + WLog_ERR(TAG, "failed to open, name: %s, device: %s", name, device); + } + else + { + WLog_DBG(TAG, "name: %s, device: %s", name, device); + } + + return audio; +} + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device) +{ + ITSMFAudioDevice* audio = NULL; + + if (name) + { + audio = tsmf_load_audio_device_by_name(name, device); + } + else + { +#if defined(WITH_PULSE) + if (!audio) + audio = tsmf_load_audio_device_by_name("pulse", device); +#endif + +#if defined(WITH_OSS) + if (!audio) + audio = tsmf_load_audio_device_by_name("oss", device); +#endif + +#if defined(WITH_ALSA) + if (!audio) + audio = tsmf_load_audio_device_by_name("alsa", device); +#endif + } + + if (audio == NULL) + { + WLog_ERR(TAG, "no sound device."); + } + else + { + WLog_DBG(TAG, "name: %s, device: %s", name, device); + } + + return audio; +} diff --git a/channels/tsmf/client/tsmf_audio.h b/channels/tsmf/client/tsmf_audio.h new file mode 100644 index 0000000..fc783d0 --- /dev/null +++ b/channels/tsmf/client/tsmf_audio.h @@ -0,0 +1,51 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Audio Device Manager + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H +#define FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H + +#include "tsmf_types.h" + +typedef struct s_ITSMFAudioDevice ITSMFAudioDevice; + +struct s_ITSMFAudioDevice +{ + /* Open the audio device. */ + BOOL (*Open)(ITSMFAudioDevice* audio, const char* device); + /* Set the audio data format. */ + BOOL(*SetFormat) + (ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, UINT32 bits_per_sample); + /* Play audio data. */ + BOOL (*Play)(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size); + /* Get the latency of the last written sample, in 100ns */ + UINT64 (*GetLatency)(ITSMFAudioDevice* audio); + /* Change the playback volume level */ + BOOL (*ChangeVolume)(ITSMFAudioDevice* audio, UINT32 newVolume, UINT32 muted); + /* Flush queued audio data */ + BOOL (*Flush)(ITSMFAudioDevice* audio); + /* Free the audio device */ + void (*Free)(ITSMFAudioDevice* audio); +}; + +#define TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME "TSMFAudioDeviceEntry" +typedef ITSMFAudioDevice* (*TSMF_AUDIO_DEVICE_ENTRY)(void); + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H */ diff --git a/channels/tsmf/client/tsmf_codec.c b/channels/tsmf/client/tsmf_codec.c new file mode 100644 index 0000000..d3f6707 --- /dev/null +++ b/channels/tsmf/client/tsmf_codec.c @@ -0,0 +1,614 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Codec + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * + * 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. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/stream.h> +#include <winpr/print.h> + +#include "tsmf_decoder.h" +#include "tsmf_constants.h" +#include "tsmf_types.h" + +#include "tsmf_codec.h" + +#include <freerdp/log.h> + +#define TAG CHANNELS_TAG("tsmf.client") + +typedef struct +{ + BYTE guid[16]; + const char* name; + int type; +} TSMFMediaTypeMap; + +static const TSMFMediaTypeMap tsmf_major_type_map[] = { + /* 73646976-0000-0010-8000-00AA00389B71 */ + { { 0x76, 0x69, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIATYPE_Video", + TSMF_MAJOR_TYPE_VIDEO }, + + /* 73647561-0000-0010-8000-00AA00389B71 */ + { { 0x61, 0x75, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIATYPE_Audio", + TSMF_MAJOR_TYPE_AUDIO }, + + { { 0 }, "Unknown", TSMF_MAJOR_TYPE_UNKNOWN } +}; + +static const TSMFMediaTypeMap tsmf_sub_type_map[] = { + /* 31435657-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WVC1", + TSMF_SUB_TYPE_WVC1 }, + + /* 00000160-0000-0010-8000-00AA00389B71 */ + { { 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMAudioV1", /* V7, V8 has the same GUID */ + TSMF_SUB_TYPE_WMA1 }, + + /* 00000161-0000-0010-8000-00AA00389B71 */ + { { 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMAudioV2", /* V7, V8 has the same GUID */ + TSMF_SUB_TYPE_WMA2 }, + + /* 00000162-0000-0010-8000-00AA00389B71 */ + { { 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMAudioV9", + TSMF_SUB_TYPE_WMA9 }, + + /* 00000055-0000-0010-8000-00AA00389B71 */ + { { 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP3", + TSMF_SUB_TYPE_MP3 }, + + /* E06D802B-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "MEDIASUBTYPE_MPEG2_AUDIO", + TSMF_SUB_TYPE_MP2A }, + + /* E06D8026-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0x26, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "MEDIASUBTYPE_MPEG2_VIDEO", + TSMF_SUB_TYPE_MP2V }, + + /* 31564D57-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x4D, 0x56, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMV1", + TSMF_SUB_TYPE_WMV1 }, + + /* 32564D57-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x4D, 0x56, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMV2", + TSMF_SUB_TYPE_WMV2 }, + + /* 33564D57-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x4D, 0x56, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMV3", + TSMF_SUB_TYPE_WMV3 }, + + /* 00001610-0000-0010-8000-00AA00389B71 */ + { { 0x10, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MPEG_HEAAC", + TSMF_SUB_TYPE_AAC }, + + /* 34363248-0000-0010-8000-00AA00389B71 */ + { { 0x48, 0x32, 0x36, 0x34, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_H264", + TSMF_SUB_TYPE_H264 }, + + /* 31435641-0000-0010-8000-00AA00389B71 */ + { { 0x41, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_AVC1", + TSMF_SUB_TYPE_AVC1 }, + + /* 3334504D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x50, 0x34, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP43", + TSMF_SUB_TYPE_MP43 }, + + /* 5634504D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x50, 0x34, 0x56, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP4S", + TSMF_SUB_TYPE_MP4S }, + + /* 3234504D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x50, 0x34, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP42", + TSMF_SUB_TYPE_MP42 }, + + /* 3253344D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP42", + TSMF_SUB_TYPE_M4S2 }, + + /* E436EB81-524F-11CE-9F53-0020AF0BA770 */ + { { 0x81, 0xEB, 0x36, 0xE4, 0x4F, 0x52, 0xCE, 0x11, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, + 0x70 }, + "MEDIASUBTYPE_MP1V", + TSMF_SUB_TYPE_MP1V }, + + /* 00000050-0000-0010-8000-00AA00389B71 */ + { { 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP1A", + TSMF_SUB_TYPE_MP1A }, + + /* E06D802C-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "MEDIASUBTYPE_DOLBY_AC3", + TSMF_SUB_TYPE_AC3 }, + + /* 32595559-0000-0010-8000-00AA00389B71 */ + { { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_YUY2", + TSMF_SUB_TYPE_YUY2 }, + + /* Opencodec IDS */ + { { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_FLAC", + TSMF_SUB_TYPE_FLAC }, + + { { 0x61, 0x34, 0x70, 0x6D, 0x7A, 0x76, 0x4D, 0x49, 0xB4, 0x78, 0xF2, 0x9D, 0x25, 0xDC, 0x90, + 0x37 }, + "MEDIASUBTYPE_OGG", + TSMF_SUB_TYPE_OGG }, + + { { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_H263", + TSMF_SUB_TYPE_H263 }, + + /* WebMMF codec IDS */ + { { 0x56, 0x50, 0x38, 0x30, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_VP8", + TSMF_SUB_TYPE_VP8 }, + + { { 0x0B, 0xD1, 0x2F, 0x8D, 0x41, 0x58, 0x6B, 0x4A, 0x89, 0x05, 0x58, 0x8F, 0xEC, 0x1A, 0xDE, + 0xD9 }, + "MEDIASUBTYPE_OGG", + TSMF_SUB_TYPE_OGG }, + + { { 0 }, "Unknown", TSMF_SUB_TYPE_UNKNOWN } + +}; + +static const TSMFMediaTypeMap tsmf_format_type_map[] = { + /* AED4AB2D-7326-43CB-9464-C879CAB9C43D */ + { { 0x2D, 0xAB, 0xD4, 0xAE, 0x26, 0x73, 0xCB, 0x43, 0x94, 0x64, 0xC8, 0x79, 0xCA, 0xB9, 0xC4, + 0x3D }, + "FORMAT_MFVideoFormat", + TSMF_FORMAT_TYPE_MFVIDEOFORMAT }, + + /* 05589F81-C356-11CE-BF01-00AA0055595A */ + { { 0x81, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, + 0x5A }, + "FORMAT_WaveFormatEx", + TSMF_FORMAT_TYPE_WAVEFORMATEX }, + + /* E06D80E3-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0xE3, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "FORMAT_MPEG2_VIDEO", + TSMF_FORMAT_TYPE_MPEG2VIDEOINFO }, + + /* F72A76A0-EB0A-11D0-ACE4-0000C0CC16BA */ + { { 0xA0, 0x76, 0x2A, 0xF7, 0x0A, 0xEB, 0xD0, 0x11, 0xAC, 0xE4, 0x00, 0x00, 0xC0, 0xCC, 0x16, + 0xBA }, + "FORMAT_VideoInfo2", + TSMF_FORMAT_TYPE_VIDEOINFO2 }, + + /* 05589F82-C356-11CE-BF01-00AA0055595A */ + { { 0x82, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, + 0x5A }, + "FORMAT_MPEG1_VIDEO", + TSMF_FORMAT_TYPE_MPEG1VIDEOINFO }, + + { { 0 }, "Unknown", TSMF_FORMAT_TYPE_UNKNOWN } +}; + +static void tsmf_print_guid(const BYTE* guid) +{ +#ifdef WITH_DEBUG_TSMF + char guidString[37]; + + snprintf(guidString, sizeof(guidString), + "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 + "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 + "%02" PRIX8 "%02" PRIX8 "", + guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6], guid[8], + guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]); + + WLog_INFO(TAG, "%s", guidString); +#endif +} + +/* http://msdn.microsoft.com/en-us/library/dd318229.aspx */ +static UINT32 tsmf_codec_parse_BITMAPINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s, + BOOL bypass) +{ + UINT32 biSize = 0; + UINT32 biWidth = 0; + UINT32 biHeight = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 40)) + return 0; + Stream_Read_UINT32(s, biSize); + Stream_Read_UINT32(s, biWidth); + Stream_Read_UINT32(s, biHeight); + Stream_Seek(s, 28); + + if (mediatype->Width == 0) + mediatype->Width = biWidth; + + if (mediatype->Height == 0) + mediatype->Height = biHeight; + + /* Assume there will be no color table for video? */ + if (biSize < 40) + return 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, (biSize - 40))) + return 0; + + if (bypass && biSize > 40) + Stream_Seek(s, biSize - 40); + + return (bypass ? biSize : 40); +} + +/* http://msdn.microsoft.com/en-us/library/dd407326.aspx */ +static UINT32 tsmf_codec_parse_VIDEOINFOHEADER2(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + UINT64 AvgTimePerFrame = 0; + + /* VIDEOINFOHEADER2.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 72)) + return 0; + + Stream_Seek_UINT32(s); + Stream_Seek_UINT32(s); + Stream_Read_UINT32(s, mediatype->Width); + Stream_Read_UINT32(s, mediatype->Height); + /* VIDEOINFOHEADER2.rcTarget */ + Stream_Seek(s, 16); + /* VIDEOINFOHEADER2.dwBitRate */ + Stream_Read_UINT32(s, mediatype->BitRate); + /* VIDEOINFOHEADER2.dwBitErrorRate */ + Stream_Seek_UINT32(s); + /* VIDEOINFOHEADER2.AvgTimePerFrame */ + Stream_Read_UINT64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL); + /* Remaining fields before bmiHeader */ + Stream_Seek(s, 24); + return 72; +} + +/* http://msdn.microsoft.com/en-us/library/dd390700.aspx */ +static UINT32 tsmf_codec_parse_VIDEOINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + /* + typedef struct { + RECT rcSource; //16 + RECT rcTarget; //16 32 + DWORD dwBitRate; //4 36 + DWORD dwBitErrorRate; //4 40 + REFERENCE_TIME AvgTimePerFrame; //8 48 + BITMAPINFOHEADER bmiHeader; + } VIDEOINFOHEADER; + */ + UINT64 AvgTimePerFrame = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 48)) + return 0; + + /* VIDEOINFOHEADER.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */ + Stream_Seek_UINT32(s); + Stream_Seek_UINT32(s); + Stream_Read_UINT32(s, mediatype->Width); + Stream_Read_UINT32(s, mediatype->Height); + /* VIDEOINFOHEADER.rcTarget */ + Stream_Seek(s, 16); + /* VIDEOINFOHEADER.dwBitRate */ + Stream_Read_UINT32(s, mediatype->BitRate); + /* VIDEOINFOHEADER.dwBitErrorRate */ + Stream_Seek_UINT32(s); + /* VIDEOINFOHEADER.AvgTimePerFrame */ + Stream_Read_UINT64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL); + return 48; +} + +static BOOL tsmf_read_format_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s, UINT32 cbFormat) +{ + UINT32 i = 0; + UINT32 j = 0; + + switch (mediatype->FormatType) + { + case TSMF_FORMAT_TYPE_MFVIDEOFORMAT: + /* http://msdn.microsoft.com/en-us/library/aa473808.aspx */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 176)) + return FALSE; + + Stream_Seek(s, 8); /* dwSize and ? */ + Stream_Read_UINT32(s, mediatype->Width); /* videoInfo.dwWidth */ + Stream_Read_UINT32(s, mediatype->Height); /* videoInfo.dwHeight */ + Stream_Seek(s, 32); + /* videoInfo.FramesPerSecond */ + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator); + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Denominator); + Stream_Seek(s, 80); + Stream_Read_UINT32(s, mediatype->BitRate); /* compressedInfo.AvgBitrate */ + Stream_Seek(s, 36); + + if (cbFormat > 176) + { + const size_t nsize = cbFormat - 176; + if (mediatype->ExtraDataSize < nsize) + return FALSE; + if (!Stream_CheckAndLogRequiredLength(TAG, s, nsize)) + return FALSE; + mediatype->ExtraDataSize = nsize; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_WAVEFORMATEX: + /* http://msdn.microsoft.com/en-us/library/dd757720.aspx */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 18)) + return FALSE; + + Stream_Seek_UINT16(s); + Stream_Read_UINT16(s, mediatype->Channels); + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator); + mediatype->SamplesPerSecond.Denominator = 1; + Stream_Read_UINT32(s, mediatype->BitRate); + mediatype->BitRate *= 8; + Stream_Read_UINT16(s, mediatype->BlockAlign); + Stream_Read_UINT16(s, mediatype->BitsPerSample); + Stream_Read_UINT16(s, mediatype->ExtraDataSize); + + if (mediatype->ExtraDataSize > 0) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_MPEG1VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390700.aspx */ + i = tsmf_codec_parse_VIDEOINFOHEADER(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_MPEG2VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390707.aspx */ + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_VIDEOINFO2: + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, FALSE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + default: + WLog_INFO(TAG, "unhandled format type 0x%x", mediatype->FormatType); + break; + } + return TRUE; +} + +BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + UINT32 cbFormat = 0; + BOOL ret = TRUE; + + ZeroMemory(mediatype, sizeof(TS_AM_MEDIA_TYPE)); + + /* MajorType */ + DEBUG_TSMF("MediaMajorType:"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + size_t i = 0; + for (; tsmf_major_type_map[i].type != TSMF_MAJOR_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_major_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->MajorType = tsmf_major_type_map[i].type; + if (mediatype->MajorType == TSMF_MAJOR_TYPE_UNKNOWN) + ret = FALSE; + + DEBUG_TSMF("MediaMajorType %s", tsmf_major_type_map[i].name); + Stream_Seek(s, 16); + + /* SubType */ + DEBUG_TSMF("MediaSubType:"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_sub_type_map[i].type != TSMF_SUB_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_sub_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->SubType = tsmf_sub_type_map[i].type; + if (mediatype->SubType == TSMF_SUB_TYPE_UNKNOWN) + ret = FALSE; + + DEBUG_TSMF("MediaSubType %s", tsmf_sub_type_map[i].name); + Stream_Seek(s, 16); + + /* bFixedSizeSamples, bTemporalCompression, SampleSize */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return FALSE; + Stream_Seek(s, 12); + + /* FormatType */ + DEBUG_TSMF("FormatType:"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_format_type_map[i].type != TSMF_FORMAT_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_format_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->FormatType = tsmf_format_type_map[i].type; + if (mediatype->FormatType == TSMF_FORMAT_TYPE_UNKNOWN) + ret = FALSE; + + DEBUG_TSMF("FormatType %s", tsmf_format_type_map[i].name); + Stream_Seek(s, 16); + + /* cbFormat */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + Stream_Read_UINT32(s, cbFormat); + DEBUG_TSMF("cbFormat %" PRIu32 "", cbFormat); +#ifdef WITH_DEBUG_TSMF + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(s), cbFormat); +#endif + + ret = tsmf_read_format_type(mediatype, s, cbFormat); + + if (mediatype->SamplesPerSecond.Numerator == 0) + mediatype->SamplesPerSecond.Numerator = 1; + + if (mediatype->SamplesPerSecond.Denominator == 0) + mediatype->SamplesPerSecond.Denominator = 1; + + return ret; +} + +BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s) +{ + size_t pos = 0; + BOOL ret = FALSE; + TS_AM_MEDIA_TYPE mediatype; + + static BOOL decoderAvailable = FALSE; + static BOOL firstRun = TRUE; + + if (firstRun) + { + firstRun = FALSE; + if (tsmf_check_decoder_available(decoder_name)) + decoderAvailable = TRUE; + } + + pos = Stream_GetPosition(s); + if (decoderAvailable) + ret = tsmf_codec_parse_media_type(&mediatype, s); + Stream_SetPosition(s, pos); + + if (ret) + { + ITSMFDecoder* decoder = tsmf_load_decoder(decoder_name, &mediatype); + + if (!decoder) + { + WLog_WARN(TAG, "Format not supported by decoder %s", decoder_name); + ret = FALSE; + } + else + { + decoder->Free(decoder); + } + } + + return ret; +} diff --git a/channels/tsmf/client/tsmf_codec.h b/channels/tsmf/client/tsmf_codec.h new file mode 100644 index 0000000..ab98899 --- /dev/null +++ b/channels/tsmf/client/tsmf_codec.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Codec + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H +#define FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H + +#include "tsmf_types.h" + +BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s); +BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H */ diff --git a/channels/tsmf/client/tsmf_constants.h b/channels/tsmf/client/tsmf_constants.h new file mode 100644 index 0000000..43d37f2 --- /dev/null +++ b/channels/tsmf/client/tsmf_constants.h @@ -0,0 +1,139 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Constants + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * + * 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H +#define FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H + +#define GUID_SIZE 16 +#define TSMF_BUFFER_PADDING_SIZE 8 + +/* Interface IDs defined in [MS-RDPEV]. There's no constant names in the MS + documentation, so we create them on our own. */ +#define TSMF_INTERFACE_DEFAULT 0x00000000 +#define TSMF_INTERFACE_CLIENT_NOTIFICATIONS 0x00000001 +#define TSMF_INTERFACE_CAPABILITIES 0x00000002 + +/* Interface ID Mask */ +#define STREAM_ID_STUB 0x80000000 +#define STREAM_ID_PROXY 0x40000000 +#define STREAM_ID_NONE 0x00000000 + +/* Functon ID */ +/* Common IDs for all interfaces are as follows. */ +#define RIMCALL_RELEASE 0x00000001 +#define RIMCALL_QUERYINTERFACE 0x00000002 +/* Capabilities Negotiator Interface IDs are as follows. */ +#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100 +/* The Client Notifications Interface ID is as follows. */ +#define PLAYBACK_ACK 0x00000100 +#define CLIENT_EVENT_NOTIFICATION 0x00000101 +/* Server Data Interface IDs are as follows. */ +#define EXCHANGE_CAPABILITIES_REQ 0x00000100 +#define SET_CHANNEL_PARAMS 0x00000101 +#define ADD_STREAM 0x00000102 +#define ON_SAMPLE 0x00000103 +#define SET_VIDEO_WINDOW 0x00000104 +#define ON_NEW_PRESENTATION 0x00000105 +#define SHUTDOWN_PRESENTATION_REQ 0x00000106 +#define SET_TOPOLOGY_REQ 0x00000107 +#define CHECK_FORMAT_SUPPORT_REQ 0x00000108 +#define ON_PLAYBACK_STARTED 0x00000109 +#define ON_PLAYBACK_PAUSED 0x0000010a +#define ON_PLAYBACK_STOPPED 0x0000010b +#define ON_PLAYBACK_RESTARTED 0x0000010c +#define ON_PLAYBACK_RATE_CHANGED 0x0000010d +#define ON_FLUSH 0x0000010e +#define ON_STREAM_VOLUME 0x0000010f +#define ON_CHANNEL_VOLUME 0x00000110 +#define ON_END_OF_STREAM 0x00000111 +#define SET_ALLOCATOR 0x00000112 +#define NOTIFY_PREROLL 0x00000113 +#define UPDATE_GEOMETRY_INFO 0x00000114 +#define REMOVE_STREAM 0x00000115 +#define SET_SOURCE_VIDEO_RECT 0x00000116 + +/* Supported platform */ +#define MMREDIR_CAPABILITY_PLATFORM_MF 0x00000001 +#define MMREDIR_CAPABILITY_PLATFORM_DSHOW 0x00000002 +#define MMREDIR_CAPABILITY_PLATFORM_OTHER 0x00000004 + +/* TSMM_CLIENT_EVENT Constants */ +#define TSMM_CLIENT_EVENT_ENDOFSTREAM 0x0064 +#define TSMM_CLIENT_EVENT_STOP_COMPLETED 0x00C8 +#define TSMM_CLIENT_EVENT_START_COMPLETED 0x00C9 +#define TSMM_CLIENT_EVENT_MONITORCHANGED 0x012C + +/* TS_MM_DATA_SAMPLE.SampleExtensions */ +#define TSMM_SAMPLE_EXT_CLEANPOINT 0x00000001 +#define TSMM_SAMPLE_EXT_DISCONTINUITY 0x00000002 +#define TSMM_SAMPLE_EXT_INTERLACED 0x00000004 +#define TSMM_SAMPLE_EXT_BOTTOMFIELDFIRST 0x00000008 +#define TSMM_SAMPLE_EXT_REPEATFIELDFIRST 0x00000010 +#define TSMM_SAMPLE_EXT_SINGLEFIELD 0x00000020 +#define TSMM_SAMPLE_EXT_DERIVEDFROMTOPFIELD 0x00000040 +#define TSMM_SAMPLE_EXT_HAS_NO_TIMESTAMPS 0x00000080 +#define TSMM_SAMPLE_EXT_RELATIVE_TIMESTAMPS 0x00000100 +#define TSMM_SAMPLE_EXT_ABSOLUTE_TIMESTAMPS 0x00000200 + +/* MajorType */ +#define TSMF_MAJOR_TYPE_UNKNOWN 0 +#define TSMF_MAJOR_TYPE_VIDEO 1 +#define TSMF_MAJOR_TYPE_AUDIO 2 + +/* SubType */ +#define TSMF_SUB_TYPE_UNKNOWN 0 +#define TSMF_SUB_TYPE_WVC1 1 +#define TSMF_SUB_TYPE_WMA2 2 +#define TSMF_SUB_TYPE_WMA9 3 +#define TSMF_SUB_TYPE_MP3 4 +#define TSMF_SUB_TYPE_MP2A 5 +#define TSMF_SUB_TYPE_MP2V 6 +#define TSMF_SUB_TYPE_WMV3 7 +#define TSMF_SUB_TYPE_AAC 8 +#define TSMF_SUB_TYPE_H264 9 +#define TSMF_SUB_TYPE_AVC1 10 +#define TSMF_SUB_TYPE_AC3 11 +#define TSMF_SUB_TYPE_WMV2 12 +#define TSMF_SUB_TYPE_WMV1 13 +#define TSMF_SUB_TYPE_MP1V 14 +#define TSMF_SUB_TYPE_MP1A 15 +#define TSMF_SUB_TYPE_YUY2 16 +#define TSMF_SUB_TYPE_MP43 17 +#define TSMF_SUB_TYPE_MP4S 18 +#define TSMF_SUB_TYPE_MP42 19 +#define TSMF_SUB_TYPE_OGG 20 +#define TSMF_SUB_TYPE_SPEEX 21 +#define TSMF_SUB_TYPE_THEORA 22 +#define TSMF_SUB_TYPE_FLAC 23 +#define TSMF_SUB_TYPE_VP8 24 +#define TSMF_SUB_TYPE_VP9 25 +#define TSMF_SUB_TYPE_H263 26 +#define TSMF_SUB_TYPE_M4S2 27 +#define TSMF_SUB_TYPE_WMA1 28 + +/* FormatType */ +#define TSMF_FORMAT_TYPE_UNKNOWN 0 +#define TSMF_FORMAT_TYPE_MFVIDEOFORMAT 1 +#define TSMF_FORMAT_TYPE_WAVEFORMATEX 2 +#define TSMF_FORMAT_TYPE_MPEG2VIDEOINFO 3 +#define TSMF_FORMAT_TYPE_VIDEOINFO2 4 +#define TSMF_FORMAT_TYPE_MPEG1VIDEOINFO 5 + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H */ diff --git a/channels/tsmf/client/tsmf_decoder.c b/channels/tsmf/client/tsmf_decoder.c new file mode 100644 index 0000000..0e318f7 --- /dev/null +++ b/channels/tsmf/client/tsmf_decoder.c @@ -0,0 +1,120 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Decoder + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <freerdp/addin.h> +#include <freerdp/client/channels.h> + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +static ITSMFDecoder* tsmf_load_decoder_by_name(const char* name) +{ + ITSMFDecoder* decoder = NULL; + TSMF_DECODER_ENTRY entry = NULL; + + entry = (TSMF_DECODER_ENTRY)(void*)freerdp_load_channel_addin_entry("tsmf", name, "decoder", 0); + + if (!entry) + return NULL; + + decoder = entry(); + + if (!decoder) + { + WLog_ERR(TAG, "failed to call export function in %s", name); + return NULL; + } + + return decoder; +} + +static BOOL tsmf_decoder_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + if (decoder->SetFormat(decoder, media_type)) + return TRUE; + else + return FALSE; +} + +ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type) +{ + ITSMFDecoder* decoder = NULL; + + if (name) + { + decoder = tsmf_load_decoder_by_name(name); + } + +#if defined(WITH_GSTREAMER_1_0) + if (!decoder) + decoder = tsmf_load_decoder_by_name("gstreamer"); +#endif + +#if defined(WITH_VIDEO_FFMPEG) + if (!decoder) + decoder = tsmf_load_decoder_by_name("ffmpeg"); +#endif + + if (decoder) + { + if (!tsmf_decoder_set_format(decoder, media_type)) + { + decoder->Free(decoder); + decoder = NULL; + } + } + + return decoder; +} + +BOOL tsmf_check_decoder_available(const char* name) +{ + ITSMFDecoder* decoder = NULL; + BOOL retValue = FALSE; + + if (name) + { + decoder = tsmf_load_decoder_by_name(name); + } +#if defined(WITH_GSTREAMER_1_0) + if (!decoder) + decoder = tsmf_load_decoder_by_name("gstreamer"); +#endif + +#if defined(WITH_VIDEO_FFMPEG) + if (!decoder) + decoder = tsmf_load_decoder_by_name("ffmpeg"); +#endif + + if (decoder) + { + decoder->Free(decoder); + decoder = NULL; + retValue = TRUE; + } + + return retValue; +} diff --git a/channels/tsmf/client/tsmf_decoder.h b/channels/tsmf/client/tsmf_decoder.h new file mode 100644 index 0000000..6b7833e --- /dev/null +++ b/channels/tsmf/client/tsmf_decoder.h @@ -0,0 +1,78 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Decoder + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * + * 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H +#define FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H + +#include "tsmf_types.h" + +typedef enum +{ + Control_Pause, + Control_Resume, + Control_Restart, + Control_Stop +} ITSMFControlMsg; + +typedef struct s_ITSMFDecoder ITSMFDecoder; + +struct s_ITSMFDecoder +{ + /* Set the decoder format. Return true if supported. */ + BOOL (*SetFormat)(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type); + /* Decode a sample. */ + BOOL (*Decode)(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, UINT32 extensions); + /* Get the decoded data */ + BYTE* (*GetDecodedData)(ITSMFDecoder* decoder, UINT32* size); + /* Get the pixel format of decoded video frame */ + UINT32 (*GetDecodedFormat)(ITSMFDecoder* decoder); + /* Get the width and height of decoded video frame */ + BOOL (*GetDecodedDimension)(ITSMFDecoder* decoder, UINT32* width, UINT32* height); + /* Free the decoder */ + void (*Free)(ITSMFDecoder* decoder); + /* Optional Contol function */ + BOOL (*Control)(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg); + /* Decode a sample with extended interface. */ + BOOL(*DecodeEx) + (ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, UINT32 extensions, + UINT64 start_time, UINT64 end_time, UINT64 duration); + /* Get current play time */ + UINT64 (*GetRunningTime)(ITSMFDecoder* decoder); + /* Update Gstreamer Rendering Area */ + BOOL(*UpdateRenderingArea) + (ITSMFDecoder* decoder, int newX, int newY, int newWidth, int newHeight, int numRectangles, + RDP_RECT* rectangles); + /* Change Gstreamer Audio Volume */ + BOOL (*ChangeVolume)(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted); + /* Check buffer level */ + BOOL (*BufferLevel)(ITSMFDecoder* decoder); + /* Register a callback for frame ack. */ + BOOL (*SetAckFunc)(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream); + /* Register a callback for stream seek detection. */ + BOOL (*SetSyncFunc)(ITSMFDecoder* decoder, void (*cb)(void*), void* stream); +}; + +#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry" +typedef ITSMFDecoder* (*TSMF_DECODER_ENTRY)(void); + +ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type); +BOOL tsmf_check_decoder_available(const char* name); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H */ diff --git a/channels/tsmf/client/tsmf_ifman.c b/channels/tsmf/client/tsmf_ifman.c new file mode 100644 index 0000000..2230505 --- /dev/null +++ b/channels/tsmf/client/tsmf_ifman.c @@ -0,0 +1,841 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Interface Manipulation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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. + */ + +#include <freerdp/config.h> + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> + +#include <winpr/stream.h> + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_media.h" +#include "tsmf_codec.h" + +#include "tsmf_ifman.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman) +{ + UINT32 CapabilityValue = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->input, CapabilityValue); + DEBUG_TSMF("server CapabilityValue %" PRIu32 "", CapabilityValue); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 8)) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(ifman->output, 1); /* CapabilityValue */ + Stream_Write_UINT32(ifman->output, 0); /* Result */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman) +{ + UINT32 v = 0; + UINT32 pos = 0; + UINT32 CapabilityType = 0; + UINT32 cbCapabilityLength = 0; + UINT32 numHostCapabilities = 0; + + WINPR_ASSERT(ifman); + if (!Stream_EnsureRemainingCapacity(ifman->output, ifman->input_size + 4)) + return ERROR_OUTOFMEMORY; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, ifman->input_size)) + return ERROR_INVALID_DATA; + + pos = Stream_GetPosition(ifman->output); + Stream_Copy(ifman->input, ifman->output, ifman->input_size); + Stream_SetPosition(ifman->output, pos); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, numHostCapabilities); + + for (UINT32 i = 0; i < numHostCapabilities; i++) + { + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, CapabilityType); + Stream_Read_UINT32(ifman->output, cbCapabilityLength); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, cbCapabilityLength)) + return ERROR_INVALID_DATA; + + pos = Stream_GetPosition(ifman->output); + + switch (CapabilityType) + { + case 1: /* Protocol version request */ + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, v); + DEBUG_TSMF("server protocol version %" PRIu32 "", v); + break; + + case 2: /* Supported platform */ + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4)) + return ERROR_INVALID_DATA; + + Stream_Peek_UINT32(ifman->output, v); + DEBUG_TSMF("server supported platform %" PRIu32 "", v); + /* Claim that we support both MF and DShow platforms. */ + Stream_Write_UINT32(ifman->output, MMREDIR_CAPABILITY_PLATFORM_MF | + MMREDIR_CAPABILITY_PLATFORM_DSHOW); + break; + + default: + WLog_ERR(TAG, "skipping unknown capability type %" PRIu32 "", CapabilityType); + break; + } + + Stream_SetPosition(ifman->output, pos + cbCapabilityLength); + } + + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman) +{ + UINT32 numMediaType = 0; + UINT32 PlatformCookie = 0; + UINT32 FormatSupported = 1; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->input, PlatformCookie); + Stream_Seek_UINT32(ifman->input); /* NoRolloverFlags (4 bytes) */ + Stream_Read_UINT32(ifman->input, numMediaType); + DEBUG_TSMF("PlatformCookie %" PRIu32 " numMediaType %" PRIu32 "", PlatformCookie, numMediaType); + + if (!tsmf_codec_check_media_type(ifman->decoder_name, ifman->input)) + FormatSupported = 0; + + if (FormatSupported) + DEBUG_TSMF("format ok."); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 12)) + return -1; + + Stream_Write_UINT32(ifman->output, FormatSupported); + Stream_Write_UINT32(ifman->output, PlatformCookie); + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman) +{ + UINT status = CHANNEL_RC_OK; + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + DEBUG_TSMF("Presentation already exists"); + ifman->output_pending = FALSE; + return CHANNEL_RC_OK; + } + + presentation = tsmf_presentation_new(Stream_Pointer(ifman->input), ifman->channel_callback); + + if (!presentation) + status = ERROR_OUTOFMEMORY; + else + tsmf_presentation_set_audio_device(presentation, ifman->audio_name, ifman->audio_device); + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext) +{ + UINT32 StreamId = 0; + UINT status = CHANNEL_RC_OK; + TSMF_STREAM* stream = NULL; + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + status = ERROR_NOT_FOUND; + } + else + { + Stream_Read_UINT32(ifman->input, StreamId); + Stream_Seek_UINT32(ifman->input); /* numMediaType */ + stream = tsmf_stream_new(presentation, StreamId, rdpcontext); + + if (!stream) + { + WLog_ERR(TAG, "failed to create stream"); + return ERROR_OUTOFMEMORY; + } + + if (!tsmf_stream_set_format(stream, ifman->decoder_name, ifman->input)) + { + WLog_ERR(TAG, "failed to set stream format"); + return ERROR_OUTOFMEMORY; + } + + tsmf_stream_start_threads(stream); + } + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 8)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, 1); /* TopologyReady */ + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman) +{ + int status = CHANNEL_RC_OK; + UINT32 StreamId = 0; + TSMF_STREAM* stream = NULL; + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + status = ERROR_NOT_FOUND; + } + else + { + Stream_Read_UINT32(ifman->input, StreamId); + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + tsmf_stream_free(stream); + else + status = ERROR_NOT_FOUND; + } + + ifman->output_pending = TRUE; + return status; +} + +static float tsmf_stream_read_float(wStream* s) +{ + float fValue = NAN; + UINT32 iValue = 0; + Stream_Read_UINT32(s, iValue); + CopyMemory(&fValue, &iValue, 4); + return fValue; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman) +{ + UINT status = CHANNEL_RC_OK; + float Left = NAN; + float Top = NAN; + float Right = NAN; + float Bottom = NAN; + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 32)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + status = ERROR_NOT_FOUND; + } + else + { + Left = tsmf_stream_read_float(ifman->input); /* Left (4 bytes) */ + Top = tsmf_stream_read_float(ifman->input); /* Top (4 bytes) */ + Right = tsmf_stream_read_float(ifman->input); /* Right (4 bytes) */ + Bottom = tsmf_stream_read_float(ifman->input); /* Bottom (4 bytes) */ + DEBUG_TSMF("SetSourceVideoRect: Left: %f Top: %f Right: %f Bottom: %f", Left, Top, Right, + Bottom); + } + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + tsmf_presentation_free(presentation); + else + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + if (!Stream_EnsureRemainingCapacity(ifman->output, 4)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + UINT32 newVolume = 0; + UINT32 muted = 0; + DEBUG_TSMF("on stream volume"); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, newVolume); + DEBUG_TSMF("on stream volume: new volume=[%" PRIu32 "]", newVolume); + Stream_Read_UINT32(ifman->input, muted); + DEBUG_TSMF("on stream volume: muted=[%" PRIu32 "]", muted); + + if (!tsmf_presentation_volume_changed(presentation, newVolume, muted)) + return ERROR_INVALID_OPERATION; + + ifman->output_pending = TRUE; + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF("on channel volume"); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + UINT32 channelVolume = 0; + UINT32 changedChannel = 0; + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, channelVolume); + DEBUG_TSMF("on channel volume: channel volume=[%" PRIu32 "]", channelVolume); + Stream_Read_UINT32(ifman->input, changedChannel); + DEBUG_TSMF("on stream volume: changed channel=[%" PRIu32 "]", changedChannel); + } + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + UINT32 numGeometryInfo = 0; + UINT32 Left = 0; + UINT32 Top = 0; + UINT32 Width = 0; + UINT32 Height = 0; + UINT32 cbVisibleRect = 0; + RDP_RECT* rects = NULL; + int num_rects = 0; + UINT error = CHANNEL_RC_OK; + int i = 0; + size_t pos = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 32)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (!presentation) + return ERROR_NOT_FOUND; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, numGeometryInfo); + pos = Stream_GetPosition(ifman->input); + Stream_Seek(ifman->input, 12); /* VideoWindowId (8 bytes), VideoWindowState (4 bytes) */ + Stream_Read_UINT32(ifman->input, Width); + Stream_Read_UINT32(ifman->input, Height); + Stream_Read_UINT32(ifman->input, Left); + Stream_Read_UINT32(ifman->input, Top); + Stream_SetPosition(ifman->input, pos + numGeometryInfo); + Stream_Read_UINT32(ifman->input, cbVisibleRect); + num_rects = cbVisibleRect / 16; + DEBUG_TSMF("numGeometryInfo %" PRIu32 " Width %" PRIu32 " Height %" PRIu32 " Left %" PRIu32 + " Top %" PRIu32 " cbVisibleRect %" PRIu32 " num_rects %d", + numGeometryInfo, Width, Height, Left, Top, cbVisibleRect, num_rects); + + if (num_rects > 0) + { + rects = (RDP_RECT*)calloc(num_rects, sizeof(RDP_RECT)); + + for (UINT32 i = 0; i < num_rects; i++) + { + Stream_Read_UINT16(ifman->input, rects[i].y); /* Top */ + Stream_Seek_UINT16(ifman->input); + Stream_Read_UINT16(ifman->input, rects[i].x); /* Left */ + Stream_Seek_UINT16(ifman->input); + Stream_Read_UINT16(ifman->input, rects[i].height); /* Bottom */ + Stream_Seek_UINT16(ifman->input); + Stream_Read_UINT16(ifman->input, rects[i].width); /* Right */ + Stream_Seek_UINT16(ifman->input); + rects[i].width -= rects[i].x; + rects[i].height -= rects[i].y; + DEBUG_TSMF("rect %d: %" PRId16 " %" PRId16 " %" PRId16 " %" PRId16 "", i, rects[i].x, + rects[i].y, rects[i].width, rects[i].height); + } + } + + if (!tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height, num_rects, + rects)) + return ERROR_INVALID_OPERATION; + + ifman->output_pending = TRUE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + tsmf_ifman_on_playback_paused(ifman); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + TSMF_STREAM* stream = NULL; + UINT32 StreamId = 0; + UINT64 SampleStartTime = 0; + UINT64 SampleEndTime = 0; + UINT64 ThrottleDuration = 0; + UINT32 SampleExtensions = 0; + UINT32 cbData = 0; + UINT error = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 60)) + return ERROR_INVALID_DATA; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + Stream_Seek_UINT32(ifman->input); /* numSample */ + Stream_Read_UINT64(ifman->input, SampleStartTime); + Stream_Read_UINT64(ifman->input, SampleEndTime); + Stream_Read_UINT64(ifman->input, ThrottleDuration); + Stream_Seek_UINT32(ifman->input); /* SampleFlags */ + Stream_Read_UINT32(ifman->input, SampleExtensions); + Stream_Read_UINT32(ifman->input, cbData); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, cbData)) + return ERROR_INVALID_DATA; + + DEBUG_TSMF("MessageId %" PRIu32 " StreamId %" PRIu32 " SampleStartTime %" PRIu64 + " SampleEndTime %" PRIu64 " " + "ThrottleDuration %" PRIu64 " SampleExtensions %" PRIu32 " cbData %" PRIu32 "", + ifman->message_id, StreamId, SampleStartTime, SampleEndTime, ThrottleDuration, + SampleExtensions, cbData); + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (!stream) + { + WLog_ERR(TAG, "unknown stream id"); + return ERROR_NOT_FOUND; + } + + if (!tsmf_stream_push_sample(stream, ifman->channel_callback, ifman->message_id, + SampleStartTime, SampleEndTime, ThrottleDuration, SampleExtensions, + cbData, Stream_Pointer(ifman->input))) + { + WLog_ERR(TAG, "unable to push sample"); + return ERROR_OUTOFMEMORY; + } + + if ((error = tsmf_presentation_sync(presentation))) + { + WLog_ERR(TAG, "tsmf_presentation_sync failed with error %" PRIu32 "", error); + return error; + } + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman) +{ + UINT32 StreamId = 0; + TSMF_PRESENTATION* presentation = NULL; + TSMF_STREAM* stream = NULL; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20)) + return ERROR_INVALID_DATA; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + DEBUG_TSMF("StreamId %" PRIu32 "", StreamId); + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + /* Flush message is for a stream, not the entire presentation + * therefore we only flush the stream as intended per the MS-RDPEV spec + */ + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + { + if (!tsmf_stream_flush(stream)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown stream id"); + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman) +{ + UINT32 StreamId = 0; + TSMF_STREAM* stream = NULL; + TSMF_PRESENTATION* presentation = NULL; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + + if (presentation) + { + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + tsmf_stream_end(stream, ifman->message_id, ifman->channel_callback); + } + + DEBUG_TSMF("StreamId %" PRIu32 "", StreamId); + ifman->output_pending = TRUE; + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 16)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + tsmf_presentation_start(presentation); + else + WLog_ERR(TAG, "unknown presentation id"); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_START_COMPLETED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + /* Added pause control so gstreamer pipeline can be paused accordingly */ + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_paused(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + /* Added restart control so gstreamer pipeline can be resumed accordingly */ + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_restarted(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = NULL; + DEBUG_TSMF(""); + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_stop(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_STOP_COMPLETED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_MONITORCHANGED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} diff --git a/channels/tsmf/client/tsmf_ifman.h b/channels/tsmf/client/tsmf_ifman.h new file mode 100644 index 0000000..9547f6b --- /dev/null +++ b/channels/tsmf/client/tsmf_ifman.h @@ -0,0 +1,68 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Interface Manipulation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H +#define FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H + +#include <freerdp/freerdp.h> + +typedef struct +{ + IWTSVirtualChannelCallback* channel_callback; + const char* decoder_name; + const char* audio_name; + const char* audio_device; + BYTE presentation_id[16]; + UINT32 stream_id; + UINT32 message_id; + + wStream* input; + UINT32 input_size; + wStream* output; + BOOL output_pending; + UINT32 output_interface_id; +} TSMF_IFMAN; + +UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman); +UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext); +UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman); +UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman); +UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman); +UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman); +UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman); +UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman); +UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H */ diff --git a/channels/tsmf/client/tsmf_main.c b/channels/tsmf/client/tsmf_main.c new file mode 100644 index 0000000..877ef24 --- /dev/null +++ b/channels/tsmf/client/tsmf_main.c @@ -0,0 +1,609 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/stream.h> +#include <winpr/cmdline.h> + +#include <freerdp/client/tsmf.h> + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_ifman.h" +#include "tsmf_media.h" + +#include "tsmf_main.h" + +BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id) +{ + wStream* s = NULL; + int status = -1; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + + if (!callback) + { + DEBUG_TSMF("No callback reference - unable to send eos response!"); + return FALSE; + } + + if (callback && callback->stream_id && callback->channel && callback->channel->Write) + { + s = Stream_New(NULL, 24); + + if (!s) + return FALSE; + + Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + Stream_Write_UINT32(s, message_id); + Stream_Write_UINT32(s, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(s, callback->stream_id); /* StreamId */ + Stream_Write_UINT32(s, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */ + Stream_Write_UINT32(s, 0); /* cbData */ + DEBUG_TSMF("EOS response size %" PRIuz "", Stream_GetPosition(s)); + status = callback->channel->Write(callback->channel, Stream_GetPosition(s), + Stream_Buffer(s), NULL); + + if (status) + { + WLog_ERR(TAG, "response error %d", status); + } + + Stream_Free(s, TRUE); + } + + return (status == 0); +} + +BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id, + UINT64 duration, UINT32 data_size) +{ + wStream* s = NULL; + int status = -1; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + + if (!callback) + return FALSE; + + s = Stream_New(NULL, 32); + + if (!s) + return FALSE; + + Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + Stream_Write_UINT32(s, message_id); + Stream_Write_UINT32(s, PLAYBACK_ACK); /* FunctionId */ + Stream_Write_UINT32(s, callback->stream_id); /* StreamId */ + Stream_Write_UINT64(s, duration); /* DataDuration */ + Stream_Write_UINT64(s, data_size); /* cbData */ + DEBUG_TSMF("ACK response size %" PRIuz "", Stream_GetPosition(s)); + + if (!callback->channel || !callback->channel->Write) + { + WLog_ERR(TAG, "callback=%p, channel=%p, write=%p", callback, + (callback ? callback->channel : NULL), + (callback && callback->channel ? callback->channel->Write : NULL)); + } + else + { + status = callback->channel->Write(callback->channel, Stream_GetPosition(s), + Stream_Buffer(s), NULL); + } + + if (status) + { + WLog_ERR(TAG, "response error %d", status); + } + + Stream_Free(s, TRUE); + return (status == 0); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + size_t length = 0; + wStream* input = NULL; + wStream* output = NULL; + UINT error = CHANNEL_RC_OK; + BOOL processed = FALSE; + TSMF_IFMAN ifman = { 0 }; + UINT32 MessageId = 0; + UINT32 FunctionId = 0; + UINT32 InterfaceId = 0; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + UINT32 cbSize = Stream_GetRemainingLength(data); + + /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */ + if (!Stream_CheckAndLogRequiredLength(TAG, data, 12)) + return ERROR_INVALID_DATA; + + input = data; + output = Stream_New(NULL, 256); + + if (!output) + return ERROR_OUTOFMEMORY; + + Stream_Seek(output, 8); + Stream_Read_UINT32(input, InterfaceId); /* InterfaceId (4 bytes) */ + Stream_Read_UINT32(input, MessageId); /* MessageId (4 bytes) */ + Stream_Read_UINT32(input, FunctionId); /* FunctionId (4 bytes) */ + DEBUG_TSMF("cbSize=%" PRIu32 " InterfaceId=0x%" PRIX32 " MessageId=0x%" PRIX32 + " FunctionId=0x%" PRIX32 "", + cbSize, InterfaceId, MessageId, FunctionId); + ifman.channel_callback = pChannelCallback; + ifman.decoder_name = ((TSMF_PLUGIN*)callback->plugin)->decoder_name; + ifman.audio_name = ((TSMF_PLUGIN*)callback->plugin)->audio_name; + ifman.audio_device = ((TSMF_PLUGIN*)callback->plugin)->audio_device; + CopyMemory(ifman.presentation_id, callback->presentation_id, GUID_SIZE); + ifman.stream_id = callback->stream_id; + ifman.message_id = MessageId; + ifman.input = input; + ifman.input_size = cbSize - 12; + ifman.output = output; + ifman.output_pending = FALSE; + ifman.output_interface_id = InterfaceId; + + // fprintf(stderr, "InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId: + // 0x%08"PRIX32"\n", InterfaceId, MessageId, FunctionId); + + switch (InterfaceId) + { + case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE: + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + error = tsmf_ifman_rim_exchange_capability_request(&ifman); + processed = TRUE; + break; + + case RIMCALL_RELEASE: + case RIMCALL_QUERYINTERFACE: + break; + + default: + break; + } + + break; + + case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY: + switch (FunctionId) + { + case SET_CHANNEL_PARAMS: + if (!Stream_CheckAndLogRequiredLength(TAG, input, GUID_SIZE + 4)) + { + error = ERROR_INVALID_DATA; + goto out; + } + + CopyMemory(callback->presentation_id, Stream_Pointer(input), GUID_SIZE); + Stream_Seek(input, GUID_SIZE); + Stream_Read_UINT32(input, callback->stream_id); + DEBUG_TSMF("SET_CHANNEL_PARAMS StreamId=%" PRIu32 "", callback->stream_id); + ifman.output_pending = TRUE; + processed = TRUE; + break; + + case EXCHANGE_CAPABILITIES_REQ: + error = tsmf_ifman_exchange_capability_request(&ifman); + processed = TRUE; + break; + + case CHECK_FORMAT_SUPPORT_REQ: + error = tsmf_ifman_check_format_support_request(&ifman); + processed = TRUE; + break; + + case ON_NEW_PRESENTATION: + error = tsmf_ifman_on_new_presentation(&ifman); + processed = TRUE; + break; + + case ADD_STREAM: + error = + tsmf_ifman_add_stream(&ifman, ((TSMF_PLUGIN*)callback->plugin)->rdpcontext); + processed = TRUE; + break; + + case SET_TOPOLOGY_REQ: + error = tsmf_ifman_set_topology_request(&ifman); + processed = TRUE; + break; + + case REMOVE_STREAM: + error = tsmf_ifman_remove_stream(&ifman); + processed = TRUE; + break; + + case SET_SOURCE_VIDEO_RECT: + error = tsmf_ifman_set_source_video_rect(&ifman); + processed = TRUE; + break; + + case SHUTDOWN_PRESENTATION_REQ: + error = tsmf_ifman_shutdown_presentation(&ifman); + processed = TRUE; + break; + + case ON_STREAM_VOLUME: + error = tsmf_ifman_on_stream_volume(&ifman); + processed = TRUE; + break; + + case ON_CHANNEL_VOLUME: + error = tsmf_ifman_on_channel_volume(&ifman); + processed = TRUE; + break; + + case SET_VIDEO_WINDOW: + error = tsmf_ifman_set_video_window(&ifman); + processed = TRUE; + break; + + case UPDATE_GEOMETRY_INFO: + error = tsmf_ifman_update_geometry_info(&ifman); + processed = TRUE; + break; + + case SET_ALLOCATOR: + error = tsmf_ifman_set_allocator(&ifman); + processed = TRUE; + break; + + case NOTIFY_PREROLL: + error = tsmf_ifman_notify_preroll(&ifman); + processed = TRUE; + break; + + case ON_SAMPLE: + error = tsmf_ifman_on_sample(&ifman); + processed = TRUE; + break; + + case ON_FLUSH: + error = tsmf_ifman_on_flush(&ifman); + processed = TRUE; + break; + + case ON_END_OF_STREAM: + error = tsmf_ifman_on_end_of_stream(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_STARTED: + error = tsmf_ifman_on_playback_started(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_PAUSED: + error = tsmf_ifman_on_playback_paused(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_RESTARTED: + error = tsmf_ifman_on_playback_restarted(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_STOPPED: + error = tsmf_ifman_on_playback_stopped(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_RATE_CHANGED: + error = tsmf_ifman_on_playback_rate_changed(&ifman); + processed = TRUE; + break; + + case RIMCALL_RELEASE: + case RIMCALL_QUERYINTERFACE: + break; + + default: + break; + } + + break; + + default: + break; + } + + input = NULL; + ifman.input = NULL; + + if (error) + { + WLog_ERR(TAG, "ifman data received processing error %" PRIu32 "", error); + } + + if (!processed) + { + switch (FunctionId) + { + case RIMCALL_RELEASE: + /* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE) + This message does not require a reply. */ + processed = TRUE; + ifman.output_pending = 1; + break; + + case RIMCALL_QUERYINTERFACE: + /* [MS-RDPEXPS] 2.2.2.1.2 Query Interface Response (QI_RSP) + This message is not supported in this channel. */ + processed = TRUE; + break; + } + + if (!processed) + { + WLog_ERR(TAG, + "Unknown InterfaceId: 0x%08" PRIX32 " MessageId: 0x%08" PRIX32 + " FunctionId: 0x%08" PRIX32 "\n", + InterfaceId, MessageId, FunctionId); + /* When a request is not implemented we return empty response indicating error */ + } + + processed = TRUE; + } + + if (processed && !ifman.output_pending) + { + /* Response packet does not have FunctionId */ + length = Stream_GetPosition(output); + Stream_SetPosition(output, 0); + Stream_Write_UINT32(output, ifman.output_interface_id); + Stream_Write_UINT32(output, MessageId); + DEBUG_TSMF("response size %d", length); + error = callback->channel->Write(callback->channel, length, Stream_Buffer(output), NULL); + + if (error) + { + WLog_ERR(TAG, "response error %" PRIu32 "", error); + } + } + +out: + Stream_Free(output, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + TSMF_STREAM* stream = NULL; + TSMF_PRESENTATION* presentation = NULL; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + DEBUG_TSMF(""); + + if (callback->stream_id) + { + presentation = tsmf_presentation_find_by_id(callback->presentation_id); + + if (presentation) + { + stream = tsmf_stream_find_by_id(presentation, callback->stream_id); + + if (stream) + tsmf_stream_free(stream); + } + } + + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + TSMF_CHANNEL_CALLBACK* callback = NULL; + TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*)pListenerCallback; + DEBUG_TSMF(""); + callback = (TSMF_CHANNEL_CALLBACK*)calloc(1, sizeof(TSMF_CHANNEL_CALLBACK)); + + if (!callback) + return CHANNEL_RC_NO_MEMORY; + + callback->iface.OnDataReceived = tsmf_on_data_received; + callback->iface.OnClose = tsmf_on_close; + callback->iface.OnOpen = NULL; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*)callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status = 0; + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin; + DEBUG_TSMF(""); + tsmf->listener_callback = (TSMF_LISTENER_CALLBACK*)calloc(1, sizeof(TSMF_LISTENER_CALLBACK)); + + if (!tsmf->listener_callback) + return CHANNEL_RC_NO_MEMORY; + + tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection; + tsmf->listener_callback->plugin = pPlugin; + tsmf->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener( + pChannelMgr, "TSMF", 0, (IWTSListenerCallback*)tsmf->listener_callback, &(tsmf->listener)); + tsmf->listener->pInterface = tsmf->iface.pInterface; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_plugin_terminated(IWTSPlugin* pPlugin) +{ + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin; + DEBUG_TSMF(""); + free(tsmf->listener_callback); + free(tsmf); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_process_addin_args(IWTSPlugin* pPlugin, const ADDIN_ARGV* args) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = NULL; + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin; + COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", + NULL, NULL, -1, NULL, "audio subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, + NULL, -1, NULL, "audio device name" }, + { "decoder", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>", + NULL, NULL, -1, NULL, "decoder subsystem" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } }; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, tsmf_args, flags, tsmf, NULL, NULL); + + if (status != 0) + return ERROR_INVALID_DATA; + + arg = tsmf_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys") + { + tsmf->audio_name = _strdup(arg->Value); + + if (!tsmf->audio_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "dev") + { + tsmf->audio_device = _strdup(arg->Value); + + if (!tsmf->audio_device) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "decoder") + { + tsmf->decoder_name = _strdup(arg->Value); + + if (!tsmf->decoder_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT tsmf_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT status = 0; + TSMF_PLUGIN* tsmf = NULL; + TsmfClientContext* context = NULL; + UINT error = CHANNEL_RC_NO_MEMORY; + tsmf = (TSMF_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "tsmf"); + + if (!tsmf) + { + tsmf = (TSMF_PLUGIN*)calloc(1, sizeof(TSMF_PLUGIN)); + + if (!tsmf) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + tsmf->iface.Initialize = tsmf_plugin_initialize; + tsmf->iface.Connected = NULL; + tsmf->iface.Disconnected = NULL; + tsmf->iface.Terminated = tsmf_plugin_terminated; + tsmf->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + context = (TsmfClientContext*)calloc(1, sizeof(TsmfClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_context; + } + + context->handle = (void*)tsmf; + tsmf->iface.pInterface = (void*)context; + + if (!tsmf_media_init()) + { + error = ERROR_INVALID_OPERATION; + goto error_init; + } + + status = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", &tsmf->iface); + } + + if (status == CHANNEL_RC_OK) + { + status = tsmf_process_addin_args(&tsmf->iface, pEntryPoints->GetPluginData(pEntryPoints)); + } + + return status; +error_init: + free(context); +error_context: + free(tsmf); + return error; +} diff --git a/channels/tsmf/client/tsmf_main.h b/channels/tsmf/client/tsmf_main.h new file mode 100644 index 0000000..fb783c1 --- /dev/null +++ b/channels/tsmf/client/tsmf_main.h @@ -0,0 +1,65 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H +#define FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H + +#include <freerdp/freerdp.h> + +typedef struct +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +} TSMF_LISTENER_CALLBACK; + +typedef struct +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + BYTE presentation_id[GUID_SIZE]; + UINT32 stream_id; +} TSMF_CHANNEL_CALLBACK; + +typedef struct +{ + IWTSPlugin iface; + + IWTSListener* listener; + TSMF_LISTENER_CALLBACK* listener_callback; + + const char* decoder_name; + const char* audio_name; + const char* audio_device; + + rdpContext* rdpcontext; +} TSMF_PLUGIN; + +BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id); +BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id, + UINT64 duration, UINT32 data_size); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H */ diff --git a/channels/tsmf/client/tsmf_media.c b/channels/tsmf/client/tsmf_media.c new file mode 100644 index 0000000..5f47090 --- /dev/null +++ b/channels/tsmf/client/tsmf_media.c @@ -0,0 +1,1544 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Media Container + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> + +#ifndef _WIN32 +#include <sys/time.h> +#endif + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/string.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/collections.h> + +#include <freerdp/freerdp.h> +#include <freerdp/client/tsmf.h> + +#include "tsmf_constants.h" +#include "tsmf_types.h" +#include "tsmf_decoder.h" +#include "tsmf_audio.h" +#include "tsmf_main.h" +#include "tsmf_codec.h" +#include "tsmf_media.h" + +#define AUDIO_TOLERANCE 10000000LL + +/* 1 second = 10,000,000 100ns units*/ +#define VIDEO_ADJUST_MAX 10 * 1000 * 1000 + +#define MAX_ACK_TIME 666667 + +#define AUDIO_MIN_BUFFER_LEVEL 3 +#define AUDIO_MAX_BUFFER_LEVEL 6 + +#define VIDEO_MIN_BUFFER_LEVEL 10 +#define VIDEO_MAX_BUFFER_LEVEL 30 + +struct S_TSMF_PRESENTATION +{ + BYTE presentation_id[GUID_SIZE]; + + const char* audio_name; + const char* audio_device; + + IWTSVirtualChannelCallback* channel_callback; + + UINT64 audio_start_time; + UINT64 audio_end_time; + + UINT32 volume; + UINT32 muted; + + wArrayList* stream_list; + + int x; + int y; + int width; + int height; + + int nr_rects; + void* rects; +}; + +struct S_TSMF_STREAM +{ + UINT32 stream_id; + + TSMF_PRESENTATION* presentation; + + ITSMFDecoder* decoder; + + int major_type; + int eos; + UINT32 eos_message_id; + IWTSVirtualChannelCallback* eos_channel_callback; + int delayed_stop; + UINT32 width; + UINT32 height; + + ITSMFAudioDevice* audio; + UINT32 sample_rate; + UINT32 channels; + UINT32 bits_per_sample; + + /* The start time of last played sample */ + UINT64 last_start_time; + /* The end_time of last played sample */ + UINT64 last_end_time; + /* Next sample should not start before this system time. */ + UINT64 next_start_time; + + UINT32 minBufferLevel; + UINT32 maxBufferLevel; + UINT32 currentBufferLevel; + + HANDLE play_thread; + HANDLE ack_thread; + HANDLE stopEvent; + HANDLE ready; + + wQueue* sample_list; + wQueue* sample_ack_list; + rdpContext* rdpcontext; + + BOOL seeking; +}; + +struct S_TSMF_SAMPLE +{ + UINT32 sample_id; + UINT64 start_time; + UINT64 end_time; + UINT64 duration; + UINT32 extensions; + UINT32 data_size; + BYTE* data; + UINT32 decoded_size; + UINT32 pixfmt; + + BOOL invalidTimestamps; + + TSMF_STREAM* stream; + IWTSVirtualChannelCallback* channel_callback; + UINT64 ack_time; +}; + +static wArrayList* presentation_list = NULL; +static int TERMINATING = 0; + +static void _tsmf_presentation_free(void* obj); +static void _tsmf_stream_free(void* obj); + +static UINT64 get_current_time(void) +{ + struct timeval tp; + gettimeofday(&tp, 0); + return ((UINT64)tp.tv_sec) * 10000000LL + ((UINT64)tp.tv_usec) * 10LL; +} + +static TSMF_SAMPLE* tsmf_stream_pop_sample(TSMF_STREAM* stream, int sync) +{ + UINT32 count = 0; + TSMF_STREAM* s = NULL; + TSMF_SAMPLE* sample = NULL; + BOOL pending = FALSE; + TSMF_PRESENTATION* presentation = NULL; + + if (!stream) + return NULL; + + presentation = stream->presentation; + + if (Queue_Count(stream->sample_list) < 1) + return NULL; + + if (sync) + { + if (stream->decoder) + { + if (stream->decoder->GetDecodedData) + { + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + /* Check if some other stream has earlier sample that needs to be played first + */ + /* Start time is more reliable than end time as some stream types seem to have + * incorrect end times from the server + */ + if (stream->last_start_time > AUDIO_TOLERANCE) + { + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (UINT32 index = 0; index < count; index++) + { + s = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + /* Start time is more reliable than end time as some stream types seem + * to have incorrect end times from the server + */ + if (s != stream && !s->eos && s->last_start_time && + s->last_start_time < stream->last_start_time - AUDIO_TOLERANCE) + { + DEBUG_TSMF("Pending due to audio tolerance"); + pending = TRUE; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + } + } + else + { + /* Start time is more reliable than end time as some stream types seem to have + * incorrect end times from the server + */ + if (stream->last_start_time > presentation->audio_start_time) + { + DEBUG_TSMF("Pending due to stream start time > audio start time"); + pending = TRUE; + } + } + } + } + } + + if (pending) + return NULL; + + sample = (TSMF_SAMPLE*)Queue_Dequeue(stream->sample_list); + + /* Only update stream last end time if the sample end time is valid and greater than the current + * stream end time */ + if (sample && (sample->end_time > stream->last_end_time) && (!sample->invalidTimestamps)) + stream->last_end_time = sample->end_time; + + /* Only update stream last start time if the sample start time is valid and greater than the + * current stream start time */ + if (sample && (sample->start_time > stream->last_start_time) && (!sample->invalidTimestamps)) + stream->last_start_time = sample->start_time; + + return sample; +} + +static void tsmf_sample_free(void* arg) +{ + TSMF_SAMPLE* sample = arg; + + if (!sample) + return; + + free(sample->data); + free(sample); +} + +static BOOL tsmf_sample_ack(TSMF_SAMPLE* sample) +{ + if (!sample) + return FALSE; + + return tsmf_playback_ack(sample->channel_callback, sample->sample_id, sample->duration, + sample->data_size); +} + +static BOOL tsmf_sample_queue_ack(TSMF_SAMPLE* sample) +{ + if (!sample) + return FALSE; + + if (!sample->stream) + return FALSE; + + return Queue_Enqueue(sample->stream->sample_ack_list, sample); +} + +/* Returns TRUE if no more samples are currently available + * Returns FALSE otherwise + */ +static BOOL tsmf_stream_process_ack(void* arg, BOOL force) +{ + TSMF_STREAM* stream = arg; + TSMF_SAMPLE* sample = NULL; + UINT64 ack_time = 0; + BOOL rc = FALSE; + + if (!stream) + return TRUE; + + Queue_Lock(stream->sample_ack_list); + sample = (TSMF_SAMPLE*)Queue_Peek(stream->sample_ack_list); + + if (!sample) + { + rc = TRUE; + goto finally; + } + + if (!force) + { + /* Do some min/max ack limiting if we have access to Buffer level information */ + if (stream->decoder && stream->decoder->BufferLevel) + { + /* Try to keep buffer level below max by withholding acks */ + if (stream->currentBufferLevel > stream->maxBufferLevel) + goto finally; + /* Try to keep buffer level above min by pushing acks through quickly */ + else if (stream->currentBufferLevel < stream->minBufferLevel) + goto dequeue; + } + + /* Time based acks only */ + ack_time = get_current_time(); + + if (sample->ack_time > ack_time) + goto finally; + } + +dequeue: + sample = Queue_Dequeue(stream->sample_ack_list); + + if (sample) + { + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + } + +finally: + Queue_Unlock(stream->sample_ack_list); + return rc; +} + +TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid, + IWTSVirtualChannelCallback* pChannelCallback) +{ + wObject* obj = NULL; + TSMF_PRESENTATION* presentation = NULL; + + if (!guid || !pChannelCallback) + return NULL; + + presentation = (TSMF_PRESENTATION*)calloc(1, sizeof(TSMF_PRESENTATION)); + + if (!presentation) + { + WLog_ERR(TAG, "calloc failed"); + return NULL; + } + + CopyMemory(presentation->presentation_id, guid, GUID_SIZE); + presentation->channel_callback = pChannelCallback; + presentation->volume = 5000; /* 50% */ + presentation->muted = 0; + + if (!(presentation->stream_list = ArrayList_New(TRUE))) + goto error_stream_list; + + obj = ArrayList_Object(presentation->stream_list); + if (!obj) + goto error_add; + obj->fnObjectFree = _tsmf_stream_free; + + if (!ArrayList_Append(presentation_list, presentation)) + goto error_add; + + return presentation; +error_add: + ArrayList_Free(presentation->stream_list); +error_stream_list: + free(presentation); + return NULL; +} + +static char* guid_to_string(const BYTE* guid, char* str, size_t len) +{ + if (!guid || !str) + return NULL; + + for (size_t i = 0; i < GUID_SIZE && (len > 2 * i); i++) + sprintf_s(str + (2 * i), len - 2 * i, "%02" PRIX8 "", guid[i]); + + return str; +} + +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid) +{ + UINT32 count = 0; + BOOL found = FALSE; + char guid_str[GUID_SIZE * 2 + 1] = { 0 }; + TSMF_PRESENTATION* presentation = NULL; + ArrayList_Lock(presentation_list); + count = ArrayList_Count(presentation_list); + + for (size_t index = 0; index < count; index++) + { + presentation = (TSMF_PRESENTATION*)ArrayList_GetItem(presentation_list, index); + + if (memcmp(presentation->presentation_id, guid, GUID_SIZE) == 0) + { + found = TRUE; + break; + } + } + + ArrayList_Unlock(presentation_list); + + if (!found) + WLog_WARN(TAG, "presentation id %s not found", + guid_to_string(guid, guid_str, sizeof(guid_str))); + + return (found) ? presentation : NULL; +} + +static BOOL tsmf_sample_playback_video(TSMF_SAMPLE* sample) +{ + UINT64 t = 0; + TSMF_VIDEO_FRAME_EVENT event; + TSMF_STREAM* stream = sample->stream; + TSMF_PRESENTATION* presentation = stream->presentation; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)sample->channel_callback; + TsmfClientContext* tsmf = (TsmfClientContext*)callback->plugin->pInterface; + DEBUG_TSMF("MessageId %" PRIu32 " EndTime %" PRIu64 " data_size %" PRIu32 " consumed.", + sample->sample_id, sample->end_time, sample->data_size); + + if (sample->data) + { + t = get_current_time(); + + /* Start time is more reliable than end time as some stream types seem to have incorrect + * end times from the server + */ + if (stream->next_start_time > t && + ((sample->start_time >= presentation->audio_start_time) || + ((sample->start_time < stream->last_start_time) && (!sample->invalidTimestamps)))) + { + USleep((stream->next_start_time - t) / 10); + } + + stream->next_start_time = t + sample->duration - 50000; + ZeroMemory(&event, sizeof(TSMF_VIDEO_FRAME_EVENT)); + event.frameData = sample->data; + event.frameSize = sample->decoded_size; + event.framePixFmt = sample->pixfmt; + event.frameWidth = sample->stream->width; + event.frameHeight = sample->stream->height; + event.x = presentation->x; + event.y = presentation->y; + event.width = presentation->width; + event.height = presentation->height; + + if (presentation->nr_rects > 0) + { + event.numVisibleRects = presentation->nr_rects; + event.visibleRects = (RECTANGLE_16*)calloc(event.numVisibleRects, sizeof(RECTANGLE_16)); + + if (!event.visibleRects) + { + WLog_ERR(TAG, "can't allocate memory for copy rectangles"); + return FALSE; + } + + memcpy(event.visibleRects, presentation->rects, + presentation->nr_rects * sizeof(RDP_RECT)); + presentation->nr_rects = 0; + } + +#if 0 + /* Dump a .ppm image for every 30 frames. Assuming the frame is in YUV format, we + extract the Y values to create a grayscale image. */ + static int frame_id = 0; + char buf[100]; + + if ((frame_id % 30) == 0) + { + sprintf_s(buf, sizeof(buf), "/tmp/FreeRDP_Frame_%d.ppm", frame_id); + FILE* fp = fopen(buf, "wb"); + if (fp) + { + fwrite("P5\n", 1, 3, fp); + sprintf_s(buf, sizeof(buf), "%"PRIu32" %"PRIu32"\n", sample->stream->width, + sample->stream->height); + fwrite(buf, 1, strnlen(buf, sizeof(buf)), fp); + fwrite("255\n", 1, 4, fp); + fwrite(sample->data, 1, sample->stream->width * sample->stream->height, fp); + fflush(fp); + fclose(fp); + } + } + + frame_id++; +#endif + /* The frame data ownership is passed to the event object, and is freed after the event is + * processed. */ + sample->data = NULL; + sample->decoded_size = 0; + + if (tsmf->FrameEvent) + tsmf->FrameEvent(tsmf, &event); + + free(event.frameData); + + if (event.visibleRects != NULL) + free(event.visibleRects); + } + + return TRUE; +} + +static BOOL tsmf_sample_playback_audio(TSMF_SAMPLE* sample) +{ + UINT64 latency = 0; + TSMF_STREAM* stream = sample->stream; + BOOL ret = 0; + DEBUG_TSMF("MessageId %" PRIu32 " EndTime %" PRIu64 " consumed.", sample->sample_id, + sample->end_time); + + if (stream->audio && sample->data) + { + ret = + sample->stream->audio->Play(sample->stream->audio, sample->data, sample->decoded_size); + free(sample->data); + sample->data = NULL; + sample->decoded_size = 0; + + if (stream->audio->GetLatency) + latency = stream->audio->GetLatency(stream->audio); + } + else + { + ret = TRUE; + latency = 0; + } + + sample->ack_time = latency + get_current_time(); + + /* Only update stream times if the sample timestamps are valid */ + if (!sample->invalidTimestamps) + { + stream->last_start_time = sample->start_time + latency; + stream->last_end_time = sample->end_time + latency; + stream->presentation->audio_start_time = sample->start_time + latency; + stream->presentation->audio_end_time = sample->end_time + latency; + } + + return ret; +} + +static BOOL tsmf_sample_playback(TSMF_SAMPLE* sample) +{ + BOOL ret = FALSE; + UINT32 width = 0; + UINT32 height = 0; + UINT32 pixfmt = 0; + TSMF_STREAM* stream = sample->stream; + + if (stream->decoder) + { + if (stream->decoder->DecodeEx) + { + /* Try to "sync" video buffers to audio buffers by looking at the running time for each + * stream The difference between the two running times causes an offset between audio + * and video actual render times. So, we try to adjust timestamps on the video buffer to + * match those on the audio buffer. + */ + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + TSMF_STREAM* temp_stream = NULL; + TSMF_PRESENTATION* presentation = stream->presentation; + ArrayList_Lock(presentation->stream_list); + int count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + UINT64 time_diff = 0; + temp_stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (temp_stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + UINT64 video_time = + (UINT64)stream->decoder->GetRunningTime(stream->decoder); + UINT64 audio_time = + (UINT64)temp_stream->decoder->GetRunningTime(temp_stream->decoder); + UINT64 max_adjust = VIDEO_ADJUST_MAX; + + if (video_time < audio_time) + max_adjust = -VIDEO_ADJUST_MAX; + + if (video_time > audio_time) + time_diff = video_time - audio_time; + else + time_diff = audio_time - video_time; + + time_diff = time_diff < VIDEO_ADJUST_MAX ? time_diff : max_adjust; + sample->start_time += time_diff; + sample->end_time += time_diff; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + } + + ret = stream->decoder->DecodeEx(stream->decoder, sample->data, sample->data_size, + sample->extensions, sample->start_time, + sample->end_time, sample->duration); + } + else + { + ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size, + sample->extensions); + } + } + + if (!ret) + { + WLog_ERR(TAG, "decode error, queue ack anyways"); + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + return FALSE; + } + + return TRUE; + } + + free(sample->data); + sample->data = NULL; + + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + if (stream->decoder->GetDecodedFormat) + { + pixfmt = stream->decoder->GetDecodedFormat(stream->decoder); + + if (pixfmt == ((UINT32)-1)) + { + WLog_ERR(TAG, "unable to decode video format"); + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + } + + return FALSE; + } + + sample->pixfmt = pixfmt; + } + + if (stream->decoder->GetDecodedDimension) + { + ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height); + + if (ret && (width != stream->width || height != stream->height)) + { + DEBUG_TSMF("video dimension changed to %" PRIu32 " x %" PRIu32 "", width, height); + stream->width = width; + stream->height = height; + } + } + } + + if (stream->decoder->GetDecodedData) + { + sample->data = stream->decoder->GetDecodedData(stream->decoder, &sample->decoded_size); + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + ret = tsmf_sample_playback_video(sample) && tsmf_sample_queue_ack(sample); + break; + + case TSMF_MAJOR_TYPE_AUDIO: + ret = tsmf_sample_playback_audio(sample) && tsmf_sample_queue_ack(sample); + break; + } + } + else + { + UINT64 ack_anticipation_time = get_current_time(); + BOOL buffer_filled = TRUE; + + /* Classify the buffer as filled once it reaches minimum level */ + if (stream->decoder->BufferLevel) + { + if (stream->currentBufferLevel < stream->minBufferLevel) + buffer_filled = FALSE; + } + + ack_anticipation_time += + (sample->duration / 2 < MAX_ACK_TIME) ? sample->duration / 2 : MAX_ACK_TIME; + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + { + break; + } + + case TSMF_MAJOR_TYPE_AUDIO: + { + break; + } + } + + sample->ack_time = ack_anticipation_time; + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + ret = FALSE; + } + } + + return ret; +} + +static DWORD WINAPI tsmf_stream_ack_func(LPVOID arg) +{ + HANDLE hdl[2]; + TSMF_STREAM* stream = (TSMF_STREAM*)arg; + UINT error = CHANNEL_RC_OK; + DEBUG_TSMF("in %" PRIu32 "", stream->stream_id); + hdl[0] = stream->stopEvent; + hdl[1] = Queue_Event(stream->sample_ack_list); + + while (1) + { + DWORD ev = WaitForMultipleObjects(2, hdl, FALSE, 1000); + + if (ev == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + if (stream->decoder) + if (stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + + if (stream->eos) + { + while ((stream->currentBufferLevel > 0) && !(tsmf_stream_process_ack(stream, TRUE))) + { + DEBUG_TSMF("END OF STREAM PROCESSING!"); + + if (stream->decoder && stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + else + stream->currentBufferLevel = 1; + + USleep(1000); + } + + tsmf_send_eos_response(stream->eos_channel_callback, stream->eos_message_id); + stream->eos = 0; + + if (stream->delayed_stop) + { + DEBUG_TSMF("Finishing delayed stream stop, now that eos has processed."); + tsmf_stream_flush(stream); + + if (stream->decoder && stream->decoder->Control) + stream->decoder->Control(stream->decoder, Control_Stop, NULL); + } + } + + /* Stream stopped force all of the acks to happen */ + if (ev == WAIT_OBJECT_0) + { + DEBUG_TSMF("ack: Stream stopped!"); + + while (1) + { + if (tsmf_stream_process_ack(stream, TRUE)) + break; + + USleep(1000); + } + + break; + } + + if (tsmf_stream_process_ack(stream, FALSE)) + continue; + + if (stream->currentBufferLevel > stream->minBufferLevel) + USleep(1000); + } + + if (error && stream->rdpcontext) + setChannelError(stream->rdpcontext, error, "tsmf_stream_ack_func reported an error"); + + DEBUG_TSMF("out %" PRIu32 "", stream->stream_id); + ExitThread(error); + return error; +} + +static DWORD WINAPI tsmf_stream_playback_func(LPVOID arg) +{ + HANDLE hdl[2]; + TSMF_SAMPLE* sample = NULL; + TSMF_STREAM* stream = (TSMF_STREAM*)arg; + TSMF_PRESENTATION* presentation = stream->presentation; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + DEBUG_TSMF("in %" PRIu32 "", stream->stream_id); + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO && stream->sample_rate && stream->channels && + stream->bits_per_sample) + { + if (stream->decoder) + { + if (stream->decoder->GetDecodedData) + { + stream->audio = tsmf_load_audio_device( + presentation->audio_name && presentation->audio_name[0] + ? presentation->audio_name + : NULL, + presentation->audio_device && presentation->audio_device[0] + ? presentation->audio_device + : NULL); + + if (stream->audio) + { + stream->audio->SetFormat(stream->audio, stream->sample_rate, stream->channels, + stream->bits_per_sample); + } + } + } + } + + hdl[0] = stream->stopEvent; + hdl[1] = Queue_Event(stream->sample_list); + + while (1) + { + status = WaitForMultipleObjects(2, hdl, FALSE, 1000); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + status = WaitForSingleObject(stream->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + if (stream->decoder) + if (stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + + sample = tsmf_stream_pop_sample(stream, 0); + + if (sample && !tsmf_sample_playback(sample)) + { + WLog_ERR(TAG, "error playing sample"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (stream->currentBufferLevel > stream->minBufferLevel) + USleep(1000); + } + + if (stream->audio) + { + stream->audio->Free(stream->audio); + stream->audio = NULL; + } + + if (error && stream->rdpcontext) + setChannelError(stream->rdpcontext, error, "tsmf_stream_playback_func reported an error"); + + DEBUG_TSMF("out %" PRIu32 "", stream->stream_id); + ExitThread(error); + return error; +} + +static BOOL tsmf_stream_start(TSMF_STREAM* stream) +{ + if (!stream || !stream->presentation || !stream->decoder || !stream->decoder->Control) + return TRUE; + + stream->eos = 0; + return stream->decoder->Control(stream->decoder, Control_Restart, NULL); +} + +static BOOL tsmf_stream_stop(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + /* If stopping after eos - we delay until the eos has been processed + * this allows us to process any buffers that have been acked even though + * they have not actually been completely processes by the decoder + */ + if (stream->eos) + { + DEBUG_TSMF("Setting up a delayed stop for once the eos has been processed."); + stream->delayed_stop = 1; + return TRUE; + } + /* Otherwise force stop immediately */ + else + { + DEBUG_TSMF("Stop with no pending eos response, so do it immediately."); + tsmf_stream_flush(stream); + return stream->decoder->Control(stream->decoder, Control_Stop, NULL); + } +} + +static BOOL tsmf_stream_pause(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + return stream->decoder->Control(stream->decoder, Control_Pause, NULL); +} + +static BOOL tsmf_stream_restart(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + stream->eos = 0; + return stream->decoder->Control(stream->decoder, Control_Restart, NULL); +} + +static BOOL tsmf_stream_change_volume(TSMF_STREAM* stream, UINT32 newVolume, UINT32 muted) +{ + if (!stream || !stream->decoder) + return TRUE; + + if (stream->decoder != NULL && stream->decoder->ChangeVolume) + { + return stream->decoder->ChangeVolume(stream->decoder, newVolume, muted); + } + else if (stream->audio != NULL && stream->audio->ChangeVolume) + { + return stream->audio->ChangeVolume(stream->audio, newVolume, muted); + } + + return TRUE; +} + +BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume, + UINT32 muted) +{ + TSMF_STREAM* stream = NULL; + BOOL ret = TRUE; + presentation->volume = newVolume; + presentation->muted = muted; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_change_volume(stream, newVolume, muted); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = NULL; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_pause(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = NULL; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_restart(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = NULL; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_start(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation) +{ + UINT error = 0; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + TSMF_STREAM* stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (WaitForSingleObject(stream->ready, 500) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + } + + ArrayList_Unlock(presentation->stream_list); + return CHANNEL_RC_OK; +} + +BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = NULL; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_stop(stream); + } + + ArrayList_Unlock(presentation->stream_list); + presentation->audio_start_time = 0; + presentation->audio_end_time = 0; + return ret; +} + +BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, int num_rects, + RDP_RECT* rects) +{ + TSMF_STREAM* stream = NULL; + void* tmp_rects = NULL; + BOOL ret = TRUE; + + /* The server may send messages with invalid width / height. + * Ignore those messages. */ + if (!width || !height) + return TRUE; + + /* Streams can be added/removed from the presentation and the server will resend geometry info + * when a new stream is added to the presentation. Also, num_rects is used to indicate whether + * or not the window is visible. So, always process a valid message with unchanged position/size + * and/or no visibility rects. + */ + presentation->x = x; + presentation->y = y; + presentation->width = width; + presentation->height = height; + tmp_rects = realloc(presentation->rects, sizeof(RDP_RECT) * num_rects); + + if (!tmp_rects && num_rects) + return FALSE; + + presentation->nr_rects = num_rects; + presentation->rects = tmp_rects; + if (presentation->rects) + CopyMemory(presentation->rects, rects, sizeof(RDP_RECT) * num_rects); + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (!stream->decoder) + continue; + + if (stream->decoder->UpdateRenderingArea) + { + ret = stream->decoder->UpdateRenderingArea(stream->decoder, x, y, width, height, + num_rects, rects); + } + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name, + const char* device) +{ + presentation->audio_name = name; + presentation->audio_device = device; +} + +BOOL tsmf_stream_flush(TSMF_STREAM* stream) +{ + BOOL ret = TRUE; + + // TSMF_SAMPLE* sample; + /* TODO: free lists */ + if (stream->audio) + ret = stream->audio->Flush(stream->audio); + + stream->eos = 0; + stream->eos_message_id = 0; + stream->eos_channel_callback = NULL; + stream->delayed_stop = 0; + stream->last_end_time = 0; + stream->next_start_time = 0; + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + stream->presentation->audio_start_time = 0; + stream->presentation->audio_end_time = 0; + } + + return TRUE; +} + +void _tsmf_presentation_free(void* obj) +{ + TSMF_PRESENTATION* presentation = (TSMF_PRESENTATION*)obj; + + if (presentation) + { + tsmf_presentation_stop(presentation); + ArrayList_Clear(presentation->stream_list); + ArrayList_Free(presentation->stream_list); + free(presentation->rects); + ZeroMemory(presentation, sizeof(TSMF_PRESENTATION)); + free(presentation); + } +} + +void tsmf_presentation_free(TSMF_PRESENTATION* presentation) +{ + ArrayList_Remove(presentation_list, presentation); +} + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, + rdpContext* rdpcontext) +{ + wObject* obj = NULL; + TSMF_STREAM* stream = NULL; + stream = tsmf_stream_find_by_id(presentation, stream_id); + + if (stream) + { + WLog_ERR(TAG, "duplicated stream id %" PRIu32 "!", stream_id); + return NULL; + } + + stream = (TSMF_STREAM*)calloc(1, sizeof(TSMF_STREAM)); + + if (!stream) + { + WLog_ERR(TAG, "Calloc failed"); + return NULL; + } + + stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL; + stream->currentBufferLevel = 1; + stream->seeking = FALSE; + stream->eos = 0; + stream->eos_message_id = 0; + stream->eos_channel_callback = NULL; + stream->stream_id = stream_id; + stream->presentation = presentation; + stream->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!stream->stopEvent) + goto error_stopEvent; + + stream->ready = CreateEvent(NULL, TRUE, TRUE, NULL); + + if (!stream->ready) + goto error_ready; + + stream->sample_list = Queue_New(TRUE, -1, -1); + + if (!stream->sample_list) + goto error_sample_list; + + obj = Queue_Object(stream->sample_list); + if (!obj) + goto error_sample_ack_list; + obj->fnObjectFree = tsmf_sample_free; + + stream->sample_ack_list = Queue_New(TRUE, -1, -1); + + if (!stream->sample_ack_list) + goto error_sample_ack_list; + + obj = Queue_Object(stream->sample_ack_list); + if (!obj) + goto error_play_thread; + obj->fnObjectFree = tsmf_sample_free; + + stream->play_thread = + CreateThread(NULL, 0, tsmf_stream_playback_func, stream, CREATE_SUSPENDED, NULL); + + if (!stream->play_thread) + goto error_play_thread; + + stream->ack_thread = + CreateThread(NULL, 0, tsmf_stream_ack_func, stream, CREATE_SUSPENDED, NULL); + + if (!stream->ack_thread) + goto error_ack_thread; + + if (!ArrayList_Append(presentation->stream_list, stream)) + goto error_add; + + stream->rdpcontext = rdpcontext; + return stream; +error_add: + SetEvent(stream->stopEvent); + + if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + +error_ack_thread: + SetEvent(stream->stopEvent); + + if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + +error_play_thread: + Queue_Free(stream->sample_ack_list); +error_sample_ack_list: + Queue_Free(stream->sample_list); +error_sample_list: + CloseHandle(stream->ready); +error_ready: + CloseHandle(stream->stopEvent); +error_stopEvent: + free(stream); + return NULL; +} + +void tsmf_stream_start_threads(TSMF_STREAM* stream) +{ + ResumeThread(stream->play_thread); + ResumeThread(stream->ack_thread); +} + +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id) +{ + BOOL found = FALSE; + TSMF_STREAM* stream = NULL; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (stream->stream_id == stream_id) + { + found = TRUE; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + return (found) ? stream : NULL; +} + +static void tsmf_stream_resync(void* arg) +{ + TSMF_STREAM* stream = arg; + ResetEvent(stream->ready); +} + +BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s) +{ + TS_AM_MEDIA_TYPE mediatype; + BOOL ret = TRUE; + + if (stream->decoder) + { + WLog_ERR(TAG, "duplicated call"); + return FALSE; + } + + if (!tsmf_codec_parse_media_type(&mediatype, s)) + { + WLog_ERR(TAG, "unable to parse media type"); + return FALSE; + } + + if (mediatype.MajorType == TSMF_MAJOR_TYPE_VIDEO) + { + DEBUG_TSMF("video width %" PRIu32 " height %" PRIu32 " bit_rate %" PRIu32 + " frame_rate %f codec_data %" PRIu32 "", + mediatype.Width, mediatype.Height, mediatype.BitRate, + (double)mediatype.SamplesPerSecond.Numerator / + (double)mediatype.SamplesPerSecond.Denominator, + mediatype.ExtraDataSize); + stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL; + } + else if (mediatype.MajorType == TSMF_MAJOR_TYPE_AUDIO) + { + DEBUG_TSMF("audio channel %" PRIu32 " sample_rate %" PRIu32 " bits_per_sample %" PRIu32 + " codec_data %" PRIu32 "", + mediatype.Channels, mediatype.SamplesPerSecond.Numerator, + mediatype.BitsPerSample, mediatype.ExtraDataSize); + stream->sample_rate = mediatype.SamplesPerSecond.Numerator; + stream->channels = mediatype.Channels; + stream->bits_per_sample = mediatype.BitsPerSample; + + if (stream->bits_per_sample == 0) + stream->bits_per_sample = 16; + + stream->minBufferLevel = AUDIO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = AUDIO_MAX_BUFFER_LEVEL; + } + + stream->major_type = mediatype.MajorType; + stream->width = mediatype.Width; + stream->height = mediatype.Height; + stream->decoder = tsmf_load_decoder(name, &mediatype); + ret &= tsmf_stream_change_volume(stream, stream->presentation->volume, + stream->presentation->muted); + + if (!stream->decoder) + return FALSE; + + if (stream->decoder->SetAckFunc) + ret &= stream->decoder->SetAckFunc(stream->decoder, tsmf_stream_process_ack, stream); + + if (stream->decoder->SetSyncFunc) + ret &= stream->decoder->SetSyncFunc(stream->decoder, tsmf_stream_resync, stream); + + return ret; +} + +void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id, + IWTSVirtualChannelCallback* pChannelCallback) +{ + if (!stream) + return; + + stream->eos = 1; + stream->eos_message_id = message_id; + stream->eos_channel_callback = pChannelCallback; +} + +void _tsmf_stream_free(void* obj) +{ + TSMF_STREAM* stream = (TSMF_STREAM*)obj; + + if (!stream) + return; + + tsmf_stream_stop(stream); + SetEvent(stream->stopEvent); + + if (stream->play_thread) + { + if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + return; + } + + CloseHandle(stream->play_thread); + stream->play_thread = NULL; + } + + if (stream->ack_thread) + { + if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + return; + } + + CloseHandle(stream->ack_thread); + stream->ack_thread = NULL; + } + + Queue_Free(stream->sample_list); + Queue_Free(stream->sample_ack_list); + + if (stream->decoder && stream->decoder->Free) + { + stream->decoder->Free(stream->decoder); + stream->decoder = NULL; + } + + CloseHandle(stream->stopEvent); + CloseHandle(stream->ready); + ZeroMemory(stream, sizeof(TSMF_STREAM)); + free(stream); +} + +void tsmf_stream_free(TSMF_STREAM* stream) +{ + TSMF_PRESENTATION* presentation = stream->presentation; + ArrayList_Remove(presentation->stream_list, stream); +} + +BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback, + UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration, + UINT32 extensions, UINT32 data_size, BYTE* data) +{ + TSMF_SAMPLE* sample = NULL; + SetEvent(stream->ready); + + if (TERMINATING) + return TRUE; + + sample = (TSMF_SAMPLE*)calloc(1, sizeof(TSMF_SAMPLE)); + + if (!sample) + { + WLog_ERR(TAG, "calloc sample failed!"); + return FALSE; + } + + sample->sample_id = sample_id; + sample->start_time = start_time; + sample->end_time = end_time; + sample->duration = duration; + sample->extensions = extensions; + + if ((sample->extensions & 0x00000080) || (sample->extensions & 0x00000040)) + sample->invalidTimestamps = TRUE; + else + sample->invalidTimestamps = FALSE; + + sample->stream = stream; + sample->channel_callback = pChannelCallback; + sample->data_size = data_size; + sample->data = calloc(1, data_size + TSMF_BUFFER_PADDING_SIZE); + + if (!sample->data) + { + WLog_ERR(TAG, "calloc sample->data failed!"); + free(sample); + return FALSE; + } + + CopyMemory(sample->data, data, data_size); + return Queue_Enqueue(stream->sample_list, sample); +} + +#ifndef _WIN32 + +static void tsmf_signal_handler(int s) +{ + TERMINATING = 1; + ArrayList_Free(presentation_list); + + if (s == SIGINT) + { + signal(s, SIG_DFL); + kill(getpid(), s); + } + else if (s == SIGUSR1) + { + signal(s, SIG_DFL); + } +} + +#endif + +BOOL tsmf_media_init(void) +{ + wObject* obj = NULL; +#ifndef _WIN32 + struct sigaction sigtrap; + sigtrap.sa_handler = tsmf_signal_handler; + sigemptyset(&sigtrap.sa_mask); + sigtrap.sa_flags = 0; + sigaction(SIGINT, &sigtrap, 0); + sigaction(SIGUSR1, &sigtrap, 0); +#endif + + if (!presentation_list) + { + presentation_list = ArrayList_New(TRUE); + + if (!presentation_list) + return FALSE; + + obj = ArrayList_Object(presentation_list); + if (!obj) + return FALSE; + obj->fnObjectFree = _tsmf_presentation_free; + } + + return TRUE; +} diff --git a/channels/tsmf/client/tsmf_media.h b/channels/tsmf/client/tsmf_media.h new file mode 100644 index 0000000..b98e322 --- /dev/null +++ b/channels/tsmf/client/tsmf_media.h @@ -0,0 +1,72 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Media Container + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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. + */ + +/** + * The media container maintains a global list of presentations, and a list of + * streams in each presentation. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H +#define FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H + +#include <freerdp/freerdp.h> + +typedef struct S_TSMF_PRESENTATION TSMF_PRESENTATION; + +typedef struct S_TSMF_STREAM TSMF_STREAM; + +typedef struct S_TSMF_SAMPLE TSMF_SAMPLE; + +TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid, + IWTSVirtualChannelCallback* pChannelCallback); +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid); +BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation); +UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume, + UINT32 muted); +BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, int num_rects, + RDP_RECT* rects); +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name, + const char* device); +void tsmf_presentation_free(TSMF_PRESENTATION* presentation); + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, + rdpContext* rdpcontext); +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id); +BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s); +void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id, + IWTSVirtualChannelCallback* pChannelCallback); +void tsmf_stream_free(TSMF_STREAM* stream); +BOOL tsmf_stream_flush(TSMF_STREAM* stream); + +BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback, + UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration, + UINT32 extensions, UINT32 data_size, BYTE* data); + +BOOL tsmf_media_init(void); +void tsmf_stream_start_threads(TSMF_STREAM* stream); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H */ diff --git a/channels/tsmf/client/tsmf_types.h b/channels/tsmf/client/tsmf_types.h new file mode 100644 index 0000000..708f208 --- /dev/null +++ b/channels/tsmf/client/tsmf_types.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Types + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H +#define FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H + +#include <freerdp/config.h> + +#include <freerdp/dvc.h> +#include <freerdp/types.h> +#include <freerdp/channels/log.h> + +#define TAG CHANNELS_TAG("tsmf.client") + +#ifdef WITH_DEBUG_TSMF +#define DEBUG_TSMF(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_TSMF(...) \ + do \ + { \ + } while (0) +#endif + +typedef struct +{ + int MajorType; + int SubType; + int FormatType; + + UINT32 Width; + UINT32 Height; + UINT32 BitRate; + struct + { + UINT32 Numerator; + UINT32 Denominator; + } SamplesPerSecond; + UINT32 Channels; + UINT32 BitsPerSample; + UINT32 BlockAlign; + const BYTE* ExtraData; + UINT32 ExtraDataSize; +} TS_AM_MEDIA_TYPE; + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H */ |