Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -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()

View File

@@ -0,0 +1,20 @@
set(OPTION_DEFAULT OFF)
set(OPTION_CLIENT_DEFAULT OFF)
set(OPTION_SERVER_DEFAULT OFF)
define_channel_options(
NAME
"tsmf"
TYPE
"dynamic"
DESCRIPTION
"[DEPRECATED] Video Redirection Virtual Channel Extension"
SPECIFICATIONS
"[MS-RDPEV]"
DEFAULT
${OPTION_DEFAULT}
CLIENT_DEFAULT
${OPTION_CLIENT_DEFAULT}
SERVER_DEFAULT
${OPTION_SERVER_DEFAULT}
)

View File

@@ -0,0 +1,82 @@
# 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)
freerdp_client_pc_add_requires_private("gstreamer-1.0")
endif()
if(WITH_GSTREAMER_1_0)
if(gstreamer_FOUND)
add_compile_definitions(WITH_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()

View File

@@ -0,0 +1,30 @@
# 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)
freerdp_client_pc_add_requires_private("alsa")
set(${MODULE_PREFIX}_SRCS tsmf_alsa.c)
set(${MODULE_PREFIX}_LIBS winpr freerdp ${ALSA_LIBRARIES})
include_directories(..)
include_directories(SYSTEM ${ALSA_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,238 @@
/**
* 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 = nullptr;
snd_pcm_sw_params_t* sw_params = nullptr;
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, nullptr);
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)
{
const BYTE* pindex = nullptr;
TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio;
DEBUG_TSMF("data_size %" PRIu32 "", data_size);
if (alsa->out_handle)
{
const size_t rbytes_per_frame = 1ULL * alsa->actual_channels * alsa->bytes_per_sample;
pindex = src;
const BYTE* end = pindex + data_size;
while (pindex < end)
{
const size_t len = (size_t)(end - pindex);
const size_t frames = len / rbytes_per_frame;
snd_pcm_sframes_t error = snd_pcm_writei(alsa->out_handle, pindex, frames);
if (error == -EPIPE)
{
snd_pcm_recover(alsa->out_handle, -EPIPE, 0);
error = 0;
}
else if (error < 0)
{
DEBUG_TSMF("error len %ld", 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 += (size_t)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(WINPR_ATTR_UNUSED 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(UINT VCAPITYPE alsa_freerdp_tsmf_client_audio_subsystem_entry(void* ptr))
{
ITSMFAudioDevice** sptr = (ITSMFAudioDevice**)ptr;
WINPR_ASSERT(sptr);
*sptr = nullptr;
TSMFAlsaAudioDevice* alsa = calloc(1, sizeof(TSMFAlsaAudioDevice));
if (!alsa)
return ERROR_OUTOFMEMORY;
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;
*sptr = &alsa->iface;
return CHANNEL_RC_OK;
}

View File

@@ -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" "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(SYSTEM ${FFMPEG_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,723 @@
/**
* 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"
#include "tsmf_audio.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;
const 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(nullptr);
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 = WINPR_ASSERTING_INT_CAST(int, media_type->Width);
mdecoder->codec_context->height = WINPR_ASSERTING_INT_CAST(int, media_type->Height);
mdecoder->codec_context->bit_rate = WINPR_ASSERTING_INT_CAST(int, media_type->BitRate);
mdecoder->codec_context->time_base.den =
WINPR_ASSERTING_INT_CAST(int, media_type->SamplesPerSecond.Numerator);
mdecoder->codec_context->time_base.num =
WINPR_ASSERTING_INT_CAST(int, 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 =
WINPR_ASSERTING_INT_CAST(int, media_type->SamplesPerSecond.Numerator);
mdecoder->codec_context->bit_rate = WINPR_ASSERTING_INT_CAST(int, media_type->BitRate);
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100)
mdecoder->codec_context->ch_layout.nb_channels =
WINPR_ASSERTING_INT_CAST(int, media_type->Channels);
#else
mdecoder->codec_context->channels = WINPR_ASSERTING_INT_CAST(int, media_type->Channels);
#endif
mdecoder->codec_context->block_align = WINPR_ASSERTING_INT_CAST(int, 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 = nullptr;
UINT32 size = 0;
const BYTE* s = nullptr;
TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
WINPR_PRAGMA_DIAG_PUSH
WINPR_PRAGMA_DIAG_IGNORED_QUALIFIERS
mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id);
WINPR_PRAGMA_DIAG_POP
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 =
WINPR_ASSERTING_INT_CAST(int, media_type->ExtraDataSize + 8);
if (mdecoder->codec_context->extradata_size == 0)
return FALSE;
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, nullptr) < 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 = nullptr;
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 = WINPR_C_ARRAY_INIT;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
av_init_packet(&pkt);
#endif
pkt.data = WINPR_CAST_CONST_PTR_AWAY(data, BYTE*);
pkt.size = WINPR_ASSERTING_INT_CAST(int, 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)
{
len = avcodec_receive_frame(mdecoder->codec_context, mdecoder->frame);
if (len == AVERROR(EAGAIN))
return TRUE;
}
#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);
const uint8_t* ptr[AV_NUM_DATA_POINTERS] = WINPR_C_ARRAY_INIT;
for (size_t x = 0; x < AV_NUM_DATA_POINTERS; x++)
ptr[x] = mdecoder->frame->data[x];
av_image_copy(frame->data, frame->linesize, ptr, 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,
WINPR_ATTR_UNUSED UINT32 extensions)
{
TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
int len = 0;
int frame_size = 0;
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 */
BYTE* dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F);
size_t dst_offset = (size_t)(dst - mdecoder->decoded_data);
const BYTE* src = data;
UINT32 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 = nullptr;
tmp_data = realloc(mdecoder->decoded_data, mdecoder->decoded_size_max * 2ull + 16ull);
if (!tmp_data)
return FALSE;
mdecoder->decoded_size_max = mdecoder->decoded_size_max * 2ull + 16ull;
mdecoder->decoded_data = tmp_data;
dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F);
const size_t diff = (size_t)(dst - mdecoder->decoded_data);
if (diff != 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 = diff;
}
dst += mdecoder->decoded_size;
}
#if LIBAVCODEC_VERSION_MAJOR < 52 || \
(LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20)
frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size;
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 = WINPR_C_ARRAY_INIT;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
av_init_packet(&pkt);
#endif
pkt.data = WINPR_CAST_CONST_PTR_AWAY(src, BYTE*);
pkt.size = WINPR_ASSERTING_INT_CAST(int, 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)
{
len = avcodec_receive_frame(mdecoder->codec_context, decoded_frame);
if (len == AVERROR(EAGAIN))
return TRUE;
}
#endif
if (len >= 0 && got_frame)
{
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100)
const int channels = mdecoder->codec_context->ch_layout.nb_channels;
#else
const int channels = mdecoder->codec_context->channels;
#endif
frame_size =
av_samples_get_buffer_size(nullptr, 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 = nullptr;
}
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 = nullptr;
}
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 = nullptr;
TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder;
*size = mdecoder->decoded_size;
buf = mdecoder->decoded_data;
mdecoder->decoded_data = nullptr;
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 %d", 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)
{
free(mdecoder->codec_context->extradata);
mdecoder->codec_context->extradata = nullptr;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
avcodec_free_context(&mdecoder->codec_context);
#else
if (mdecoder->prepared)
avcodec_close(mdecoder->codec_context);
av_free(mdecoder->codec_context);
#endif
}
free(decoder);
}
static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT;
static BOOL CALLBACK InitializeAvCodecs(WINPR_ATTR_UNUSED PINIT_ONCE once,
WINPR_ATTR_UNUSED PVOID param,
WINPR_ATTR_UNUSED PVOID* context)
{
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
avcodec_register_all();
#endif
return TRUE;
}
FREERDP_ENTRY_POINT(UINT VCAPITYPE ffmpeg_freerdp_tsmf_client_decoder_subsystem_entry(void* ptr))
{
ITSMFDecoder** sptr = (ITSMFDecoder**)ptr;
WINPR_ASSERT(sptr);
*sptr = nullptr;
TSMFFFmpegDecoder* decoder = nullptr;
if (!InitOnceExecuteOnce(&g_Initialized, InitializeAvCodecs, nullptr, nullptr))
return ERROR_INTERNAL_ERROR;
WLog_DBG(TAG, "TSMFDecoderEntry FFMPEG");
decoder = (TSMFFFmpegDecoder*)calloc(1, sizeof(TSMFFFmpegDecoder));
if (!decoder)
return ERROR_OUTOFMEMORY;
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;
*sptr = &decoder->iface;
return CHANNEL_RC_OK;
}

View File

@@ -0,0 +1,61 @@
# 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)
freerdp_client_pc_add_requires_private("gstreamer-base-1.0;gstreamer-video-1.0;gstreamer-app-1.0")
set(LIBS ${gstreamer_LIBRARIES} ${gstreamerbase_LIBRARIES} ${gstreamervideo_LIBRARIES} ${gstreamerapp_LIBRARIES})
include_directories(
SYSTEM ${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)
freerdp_client_pc_add_requires_private("x11")
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_compile_definitions(WITH_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 "")

View File

@@ -0,0 +1,501 @@
/*
* 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] = WINPR_C_ARRAY_INIT;
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(nullptr);
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,
nullptr);
#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 = nullptr;
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 = nullptr;
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 = nullptr;
hdl->subwin = 0;
hdl->subwinMapped = FALSE;
hdl->subwinX = -1;
hdl->subwinY = -1;
hdl->subwinWidth = -1;
hdl->subwinHeight = -1;
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/*
* 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;
WINPR_ATTR_NODISCARD
FREERDP_LOCAL const char* tsmf_platform_get_video_sink(void);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL const char* tsmf_platform_get_audio_sink(void);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_platform_create(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_platform_free(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_window_create(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width,
int height, int nr_rect, RDP_RECT* visible);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_window_destroy(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_window_map(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL int tsmf_window_unmap(TSMFGstreamerDecoder* decoder);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL BOOL tsmf_gstreamer_add_pad(TSMFGstreamerDecoder* mdecoder);
FREERDP_LOCAL void tsmf_gstreamer_remove_pad(TSMFGstreamerDecoder* mdecoder);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H */

View File

@@ -0,0 +1,29 @@
# 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(SYSTEM ${OSS_INCLUDE_DIRS})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,237 @@
/**
* 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>
#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] = WINPR_C_ARRAY_INIT; \
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 == nullptr || oss->pcm_handle != -1)
return FALSE;
if (device == nullptr) /* 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;
}
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 == nullptr || 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 = WINPR_ASSERTING_INT_CAST(int, channels);
if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1)
OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno);
tmp = WINPR_ASSERTING_INT_CAST(int, sample_rate);
if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1)
OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno);
tmp = WINPR_ASSERTING_INT_CAST(int, ((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)
{
UINT32 offset = 0;
TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
DEBUG_TSMF("tsmf_oss_play: data_size %" PRIu32 "", data_size);
if (oss == nullptr || oss->pcm_handle == -1)
return FALSE;
if (data == nullptr || data_size == 0)
return TRUE;
offset = 0;
oss->data_size_last = data_size;
while (offset < data_size)
{
const ssize_t 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 == nullptr)
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(WINPR_ATTR_UNUSED ITSMFAudioDevice* audio)
{
return TRUE;
}
static void tsmf_oss_free(ITSMFAudioDevice* audio)
{
TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio;
if (oss == nullptr)
return;
if (oss->pcm_handle != -1)
{
WLog_INFO(TAG, "close: %s", oss->dev_name);
close(oss->pcm_handle);
}
free(oss);
}
FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_tsmf_client_audio_subsystem_entry(void* ptr))
{
ITSMFAudioDevice** sptr = (ITSMFAudioDevice**)ptr;
WINPR_ASSERT(sptr);
*sptr = nullptr;
TSMFOssAudioDevice* oss = calloc(1, sizeof(TSMFOssAudioDevice));
if (!oss)
return ERROR_OUTOFMEMORY;
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;
*sptr = &oss->iface;
return CHANNEL_RC_OK;
}

View File

@@ -0,0 +1,30 @@
# 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)
freerdp_client_pc_add_requires_private("libpulse")
set(${MODULE_PREFIX}_SRCS tsmf_pulse.c)
set(${MODULE_PREFIX}_LIBS winpr ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY})
include_directories(..)
include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIR})
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@@ -0,0 +1,427 @@
/**
* 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 <freerdp/utils/helpers.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, nullptr, 0, nullptr))
{
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_getApplicationDetailsString());
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(WINPR_ATTR_UNUSED pa_stream* stream,
WINPR_ATTR_UNUSED 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 == nullptr)
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;
WINPR_ASSERT(pulse);
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(WINPR_ATTR_UNUSED pa_stream* stream,
WINPR_ATTR_UNUSED 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, nullptr, nullptr);
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 = nullptr;
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 = WINPR_C_ARRAY_INIT;
if (!pulse->context)
return FALSE;
DEBUG_TSMF("");
pa_threaded_mainloop_lock(pulse->mainloop);
pulse->stream = pa_stream_new(pulse->context, freerdp_getApplicationDetailsString(),
&pulse->sample_spec, nullptr);
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 = (uint32_t)pa_usec_to_bytes(500000, &pulse->sample_spec);
buffer_attr.tlength = (uint32_t)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 : nullptr, &buffer_attr,
PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE,
nullptr, nullptr) < 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,
WINPR_ATTR_UNUSED 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;
WINPR_ASSERT(channels <= UINT8_MAX);
pulse->sample_spec.channels = (uint8_t)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 = nullptr;
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, nullptr, 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, nullptr) == 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 = nullptr;
}
if (pulse->mainloop)
{
pa_threaded_mainloop_free(pulse->mainloop);
pulse->mainloop = nullptr;
}
free(pulse);
}
FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_tsmf_client_audio_subsystem_entry(void* ptr))
{
ITSMFAudioDevice** sptr = (ITSMFAudioDevice**)ptr;
WINPR_ASSERT(sptr);
*sptr = nullptr;
TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)calloc(1, sizeof(TSMFPulseAudioDevice));
if (!pulse)
return ERROR_OUTOFMEMORY;
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;
*sptr = &pulse->iface;
return CHANNEL_RC_OK;
}

View File

@@ -0,0 +1,99 @@
/**
* 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 = nullptr;
union
{
PVIRTUALCHANNELENTRY pvce;
TSMF_AUDIO_DEVICE_ENTRY entry;
} cnv;
cnv.pvce = freerdp_load_channel_addin_entry("tsmf", name, "audio", 0);
if (!cnv.entry)
return nullptr;
const UINT rc = cnv.entry(&audio);
if ((rc != CHANNEL_RC_OK) || !audio)
{
WLog_ERR(TAG, "failed to call export function in %s", name);
return nullptr;
}
if (!audio->Open(audio, device))
{
audio->Free(audio);
audio = nullptr;
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 = nullptr;
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 == nullptr)
{
WLog_ERR(TAG, "no sound device.");
}
else
{
WLog_DBG(TAG, "name: %s, device: %s", name, device);
}
return audio;
}

View File

@@ -0,0 +1,53 @@
/**
* 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. */
WINPR_ATTR_NODISCARD BOOL (*Open)(ITSMFAudioDevice* audio, const char* device);
/* Set the audio data format. */
WINPR_ATTR_NODISCARD BOOL (*SetFormat)(ITSMFAudioDevice* audio, UINT32 sample_rate,
UINT32 channels, UINT32 bits_per_sample);
/* Play audio data. */
WINPR_ATTR_NODISCARD BOOL (*Play)(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size);
/* Get the latency of the last written sample, in 100ns */
WINPR_ATTR_NODISCARD UINT64 (*GetLatency)(ITSMFAudioDevice* audio);
/* Change the playback volume level */
WINPR_ATTR_NODISCARD BOOL (*ChangeVolume)(ITSMFAudioDevice* audio, UINT32 newVolume,
UINT32 muted);
/* Flush queued audio data */
WINPR_ATTR_NODISCARD BOOL (*Flush)(ITSMFAudioDevice* audio);
/* Free the audio device */
void (*Free)(ITSMFAudioDevice* audio);
};
#define TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME "TSMFAudioDeviceEntry"
typedef UINT(VCAPITYPE* TSMF_AUDIO_DEVICE_ENTRY)(ITSMFAudioDevice** dev);
WINPR_ATTR_NODISCARD FREERDP_LOCAL ITSMFAudioDevice* tsmf_load_audio_device(const char* name,
const char* device);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H */

View File

@@ -0,0 +1,615 @@
/**
* 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)
{
WINPR_UNUSED(guid);
#ifdef WITH_DEBUG_TSMF
char guidString[37];
(void)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 = (UINT32)(AvgTimePerFrame / 10ULL);
/* 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 = (UINT32)(AvgTimePerFrame / 10ULL);
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 = (UINT32)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%08x", (unsigned)mediatype->FormatType);
break;
}
return TRUE;
}
BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s)
{
UINT32 cbFormat = 0;
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)
return 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)
return 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)
return 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
const BOOL 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;
}

View File

@@ -0,0 +1,31 @@
/**
* 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"
WINPR_ATTR_NODISCARD
FREERDP_LOCAL BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s);
WINPR_ATTR_NODISCARD
FREERDP_LOCAL BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H */

View File

@@ -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
/* Function 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 */

View File

@@ -0,0 +1,121 @@
/**
* 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 = nullptr;
union
{
PVIRTUALCHANNELENTRY pvce;
TSMF_DECODER_ENTRY entry;
} cnv;
cnv.pvce = freerdp_load_channel_addin_entry("tsmf", name, "decoder", 0);
if (!cnv.entry)
return nullptr;
const UINT rc = cnv.entry(&decoder);
if ((rc != CHANNEL_RC_OK) || !decoder)
{
WLog_ERR(TAG, "failed to call export function in %s", name);
return nullptr;
}
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 = nullptr;
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 = nullptr;
}
}
return decoder;
}
BOOL tsmf_check_decoder_available(const char* name)
{
ITSMFDecoder* decoder = nullptr;
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 = nullptr;
retValue = TRUE;
}
return retValue;
}

View File

@@ -0,0 +1,87 @@
/**
* 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. */
WINPR_ATTR_NODISCARD BOOL (*SetFormat)(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type);
/* Decode a sample. */
WINPR_ATTR_NODISCARD BOOL (*Decode)(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
UINT32 extensions);
/* Get the decoded data */
WINPR_ATTR_NODISCARD BYTE* (*GetDecodedData)(ITSMFDecoder* decoder, UINT32* size);
/* Get the pixel format of decoded video frame */
WINPR_ATTR_NODISCARD UINT32 (*GetDecodedFormat)(ITSMFDecoder* decoder);
/* Get the width and height of decoded video frame */
WINPR_ATTR_NODISCARD BOOL (*GetDecodedDimension)(ITSMFDecoder* decoder, UINT32* width,
UINT32* height);
/* Free the decoder */
void (*Free)(ITSMFDecoder* decoder);
/* Optional Control function */
WINPR_ATTR_NODISCARD BOOL (*Control)(ITSMFDecoder* decoder, ITSMFControlMsg control_msg,
UINT32* arg);
/* Decode a sample with extended interface. */
WINPR_ATTR_NODISCARD BOOL (*DecodeEx)(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
UINT32 extensions, UINT64 start_time, UINT64 end_time,
UINT64 duration);
/* Get current play time */
WINPR_ATTR_NODISCARD UINT64 (*GetRunningTime)(ITSMFDecoder* decoder);
/* Update Gstreamer Rendering Area */
WINPR_ATTR_NODISCARD BOOL (*UpdateRenderingArea)(ITSMFDecoder* decoder, UINT32 newX,
UINT32 newY, UINT32 newWidth, UINT32 newHeight,
UINT32 numRectangles,
const RECTANGLE_32* rectangles);
/* Change Gstreamer Audio Volume */
WINPR_ATTR_NODISCARD BOOL (*ChangeVolume)(ITSMFDecoder* decoder, UINT32 newVolume,
UINT32 muted);
/* Check buffer level */
WINPR_ATTR_NODISCARD BOOL (*BufferLevel)(ITSMFDecoder* decoder);
/* Register a callback for frame ack. */
WINPR_ATTR_NODISCARD BOOL (*SetAckFunc)(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL),
void* stream);
/* Register a callback for stream seek detection. */
WINPR_ATTR_NODISCARD BOOL (*SetSyncFunc)(ITSMFDecoder* decoder, void (*cb)(void*),
void* stream);
};
#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry"
typedef UINT(VCAPITYPE* TSMF_DECODER_ENTRY)(ITSMFDecoder** decoder);
WINPR_ATTR_NODISCARD FREERDP_LOCAL ITSMFDecoder* tsmf_load_decoder(const char* name,
TS_AM_MEDIA_TYPE* media_type);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_check_decoder_available(const char* name);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H */

View File

@@ -0,0 +1,839 @@
/**
* 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 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;
const size_t xpos = Stream_GetPosition(ifman->output);
Stream_Copy(ifman->input, ifman->output, ifman->input_size);
Stream_SetPosition(ifman->output, xpos);
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;
const size_t pos = Stream_GetPosition(ifman->output);
switch (CapabilityType)
{
case 1: /* Protocol version request */
{
if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
return ERROR_INVALID_DATA;
const UINT32 v = Stream_Get_UINT32(ifman->output);
WINPR_UNUSED(v);
DEBUG_TSMF("server protocol version %" PRIu32 "", v);
}
break;
case 2: /* Supported platform */
{
if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4))
return ERROR_INVALID_DATA;
const UINT32 v = Stream_Get_UINT32(ifman->output);
WINPR_UNUSED(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 = nullptr;
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 = nullptr;
TSMF_PRESENTATION* presentation = nullptr;
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 = nullptr;
TSMF_PRESENTATION* presentation = nullptr;
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;
TSMF_PRESENTATION* presentation = nullptr;
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
{
#ifdef WITH_DEBUG_TSMF
const float Left = tsmf_stream_read_float(ifman->input); /* Left (4 bytes) */
const float Top = tsmf_stream_read_float(ifman->input); /* Top (4 bytes) */
const float Right = tsmf_stream_read_float(ifman->input); /* Right (4 bytes) */
const float 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);
#endif
}
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 = nullptr;
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 = nullptr;
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 = nullptr;
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 = nullptr;
UINT32 numGeometryInfo = 0;
UINT32 Left = 0;
UINT32 Top = 0;
UINT32 Width = 0;
UINT32 Height = 0;
UINT32 cbVisibleRect = 0;
RECTANGLE_32* rects = nullptr;
UINT error = CHANNEL_RC_OK;
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);
const UINT32 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 = (RECTANGLE_32*)calloc(num_rects, sizeof(RECTANGLE_32));
for (size_t i = 0; i < num_rects; i++)
{
Stream_Read_UINT32(ifman->input, rects[i].top); /* Top */
Stream_Read_UINT32(ifman->input, rects[i].left); /* Left */
Stream_Read_UINT32(ifman->input, rects[i].height); /* Bottom */
Stream_Read_UINT32(ifman->input, rects[i].width); /* Right */
rects[i].width -= rects[i].left;
rects[i].height -= rects[i].top;
DEBUG_TSMF("rect %d: %" PRId16 " %" PRId16 " %" PRId16 " %" PRId16 "", i, rects[i].x,
rects[i].y, rects[i].width, rects[i].height);
}
}
const BOOL rc = tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height,
num_rects, rects);
free(rects);
if (!rc)
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 = nullptr;
TSMF_STREAM* stream = nullptr;
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 = nullptr;
TSMF_STREAM* stream = nullptr;
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 = nullptr;
TSMF_PRESENTATION* presentation = nullptr;
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 = nullptr;
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 = nullptr;
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 = nullptr;
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 = nullptr;
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;
}

View File

@@ -0,0 +1,92 @@
/**
* 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;
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT
tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman,
rdpContext* rdpcontext);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H */

View File

@@ -0,0 +1,619 @@
/**
* 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)
{
ssize_t 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)
{
wStream* s = Stream_New(nullptr, 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 */
const size_t pos = Stream_GetPosition(s);
DEBUG_TSMF("EOS response size %" PRIuz "", pos);
WINPR_ASSERT(pos <= UINT32_MAX);
status =
callback->channel->Write(callback->channel, (UINT32)pos, Stream_Buffer(s), nullptr);
if (status)
{
WLog_ERR(TAG, "response error %" PRId32, WINPR_CXX_COMPAT_CAST(int32_t, status));
}
Stream_Free(s, TRUE);
}
return (status == 0);
}
BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id,
UINT64 duration, UINT32 data_size)
{
ssize_t status = -1;
TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
if (!callback)
return FALSE;
wStream* s = Stream_New(nullptr, 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 */
const size_t pos = Stream_GetPosition(s);
DEBUG_TSMF("ACK response size %" PRIuz "", pos);
if (!callback->channel || !callback->channel->Write)
{
WLog_ERR(TAG, "channel=%p, write=%p", WINPR_CXX_COMPAT_CAST(const void*, callback->channel),
WINPR_CXX_COMPAT_CAST(const void*,
callback->channel ? callback->channel->Write : nullptr));
}
else
{
status = callback->channel->Write(
callback->channel, WINPR_ASSERTING_INT_CAST(uint32_t, pos), Stream_Buffer(s), nullptr);
}
if (status)
{
WLog_ERR(TAG, "response error %" PRId32, WINPR_CXX_COMPAT_CAST(int32_t, 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)
{
wStream* input = nullptr;
wStream* output = nullptr;
UINT error = CHANNEL_RC_OK;
BOOL processed = FALSE;
TSMF_IFMAN ifman = WINPR_C_ARRAY_INIT;
UINT32 MessageId = 0;
UINT32 FunctionId = 0;
UINT32 InterfaceId = 0;
TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback;
const size_t cbSize = Stream_GetRemainingLength(data);
/* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */
if (!Stream_CheckAndLogRequiredLength(TAG, data, 12) || (cbSize > UINT32_MAX))
return ERROR_INVALID_DATA;
input = data;
output = Stream_New(nullptr, 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 = (UINT32)(cbSize - 12U);
ifman.output = output;
ifman.output_pending = FALSE;
ifman.output_interface_id = InterfaceId;
// (void)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 = nullptr;
ifman.input = nullptr;
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;
default:
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 */
const size_t length = Stream_GetPosition(output);
if (length > UINT32_MAX)
goto out;
Stream_ResetPosition(output);
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, (UINT32)length, Stream_Buffer(output),
nullptr);
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 = nullptr;
TSMF_PRESENTATION* presentation = nullptr;
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,
WINPR_ATTR_UNUSED BYTE* Data,
WINPR_ATTR_UNUSED BOOL* pbAccept,
IWTSVirtualChannelCallback** ppCallback)
{
TSMF_CHANNEL_CALLBACK* callback = nullptr;
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 = nullptr;
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 = nullptr;
TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin;
COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
nullptr, nullptr, -1, nullptr, "audio subsystem" },
{ "dev", COMMAND_LINE_VALUE_REQUIRED, "<device>",
nullptr, nullptr, -1, nullptr, "audio device name" },
{ "decoder", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
nullptr, nullptr, -1, nullptr, "decoder subsystem" },
{ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr,
nullptr } };
flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
status = CommandLineParseArgumentsA(args->argc, args->argv, tsmf_args, flags, tsmf, nullptr,
nullptr);
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)) != nullptr);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
FREERDP_ENTRY_POINT(UINT VCAPITYPE tsmf_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
{
UINT status = 0;
TSMF_PLUGIN* tsmf = nullptr;
TsmfClientContext* context = nullptr;
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 = nullptr;
tsmf->iface.Disconnected = nullptr;
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;
}

View File

@@ -0,0 +1,68 @@
/**
* 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;
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL
tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL
tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id, UINT64 duration,
UINT32 data_size);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/**
* 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;
FREERDP_LOCAL void tsmf_presentation_free(TSMF_PRESENTATION* presentation);
WINPR_ATTR_MALLOC(tsmf_presentation_free, 1)
WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_PRESENTATION*
tsmf_presentation_new(const BYTE* guid, IWTSVirtualChannelCallback* pChannelCallback);
WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_PRESENTATION*
tsmf_presentation_find_by_id(const BYTE* guid);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation);
WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL
tsmf_presentation_restarted(TSMF_PRESENTATION* presentation);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL
tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume, UINT32 muted);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_set_geometry_info(
TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y, UINT32 width, UINT32 height,
UINT32 num_rects, const RECTANGLE_32* rects);
FREERDP_LOCAL
void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name,
const char* device);
FREERDP_LOCAL
void tsmf_stream_free(TSMF_STREAM* stream);
WINPR_ATTR_MALLOC(tsmf_stream_free, 1)
WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_STREAM*
tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, rdpContext* rdpcontext);
WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_STREAM*
tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_stream_set_format(TSMF_STREAM* stream,
const char* name, wStream* s);
FREERDP_LOCAL
void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id,
IWTSVirtualChannelCallback* pChannelCallback);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_stream_flush(TSMF_STREAM* stream);
WINPR_ATTR_NODISCARD FREERDP_LOCAL 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);
WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_media_init(void);
FREERDP_LOCAL
void tsmf_stream_start_threads(TSMF_STREAM* stream);
#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H */

View File

@@ -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 */