summaryrefslogtreecommitdiffstats
path: root/channels/tsmf
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--channels/tsmf/CMakeLists.txt22
-rw-r--r--channels/tsmf/ChannelOptions.cmake23
-rw-r--r--channels/tsmf/client/CMakeLists.txt84
-rw-r--r--channels/tsmf/client/alsa/CMakeLists.txt35
-rw-r--r--channels/tsmf/client/alsa/tsmf_alsa.c240
-rw-r--r--channels/tsmf/client/ffmpeg/CMakeLists.txt42
-rw-r--r--channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c695
-rw-r--r--channels/tsmf/client/gstreamer/CMakeLists.txt76
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_X11.c500
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_gstreamer.c1054
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_platform.h85
-rw-r--r--channels/tsmf/client/oss/CMakeLists.txt35
-rw-r--r--channels/tsmf/client/oss/tsmf_oss.c248
-rw-r--r--channels/tsmf/client/pulse/CMakeLists.txt35
-rw-r--r--channels/tsmf/client/pulse/tsmf_pulse.c414
-rw-r--r--channels/tsmf/client/tsmf_audio.c97
-rw-r--r--channels/tsmf/client/tsmf_audio.h51
-rw-r--r--channels/tsmf/client/tsmf_codec.c614
-rw-r--r--channels/tsmf/client/tsmf_codec.h28
-rw-r--r--channels/tsmf/client/tsmf_constants.h139
-rw-r--r--channels/tsmf/client/tsmf_decoder.c120
-rw-r--r--channels/tsmf/client/tsmf_decoder.h78
-rw-r--r--channels/tsmf/client/tsmf_ifman.c841
-rw-r--r--channels/tsmf/client/tsmf_ifman.h68
-rw-r--r--channels/tsmf/client/tsmf_main.c609
-rw-r--r--channels/tsmf/client/tsmf_main.h65
-rw-r--r--channels/tsmf/client/tsmf_media.c1544
-rw-r--r--channels/tsmf/client/tsmf_media.h72
-rw-r--r--channels/tsmf/client/tsmf_types.h61
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 */