From 6d6c5062aba72c8aa347f4ee10592be7598f4a78 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 19 Sep 2011 17:04:14 +0800 Subject: [PATCH 1/9] drdynvc: fix buffer pointer type. --- channels/drdynvc/drdynvc_main.c | 2 +- channels/drdynvc/drdynvc_main.h | 2 +- channels/drdynvc/drdynvc_types.h | 2 ++ channels/drdynvc/dvcman.c | 2 +- include/freerdp/dvc.h | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/channels/drdynvc/drdynvc_main.c b/channels/drdynvc/drdynvc_main.c index 26296f786..5fda6ac27 100644 --- a/channels/drdynvc/drdynvc_main.c +++ b/channels/drdynvc/drdynvc_main.c @@ -71,7 +71,7 @@ static int drdynvc_write_variable_uint(STREAM* stream, uint32 val) return cb; } -int drdynvc_write_data(drdynvcPlugin* drdynvc, uint32 ChannelId, char* data, uint32 data_size) +int drdynvc_write_data(drdynvcPlugin* drdynvc, uint32 ChannelId, uint8* data, uint32 data_size) { STREAM* data_out; uint32 pos = 0; diff --git a/channels/drdynvc/drdynvc_main.h b/channels/drdynvc/drdynvc_main.h index 2dc57429e..519c61d19 100644 --- a/channels/drdynvc/drdynvc_main.h +++ b/channels/drdynvc/drdynvc_main.h @@ -24,7 +24,7 @@ typedef struct drdynvc_plugin drdynvcPlugin; -int drdynvc_write_data(drdynvcPlugin* plugin, uint32 ChannelId, char* data, uint32 data_size); +int drdynvc_write_data(drdynvcPlugin* plugin, uint32 ChannelId, uint8* data, uint32 data_size); int drdynvc_push_event(drdynvcPlugin* plugin, RDP_EVENT* event); #endif diff --git a/channels/drdynvc/drdynvc_types.h b/channels/drdynvc/drdynvc_types.h index f62fee7f5..a08a60523 100644 --- a/channels/drdynvc/drdynvc_types.h +++ b/channels/drdynvc/drdynvc_types.h @@ -21,6 +21,8 @@ #define __DRDYNVC_TYPES_H #include "config.h" +#include +#include #include #ifdef WITH_DEBUG_DVC diff --git a/channels/drdynvc/dvcman.c b/channels/drdynvc/dvcman.c index 3fd28ad87..33d14c943 100644 --- a/channels/drdynvc/dvcman.c +++ b/channels/drdynvc/dvcman.c @@ -263,7 +263,7 @@ int dvcman_init(IWTSVirtualChannelManager* pChannelMgr) static int dvcman_write_channel(IWTSVirtualChannel* pChannel, uint32 cbSize, - char* pBuffer, + uint8* pBuffer, void* pReserved) { DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel; diff --git a/include/freerdp/dvc.h b/include/freerdp/dvc.h index 32d3808f9..dfa487c28 100644 --- a/include/freerdp/dvc.h +++ b/include/freerdp/dvc.h @@ -73,7 +73,7 @@ struct _IWTSVirtualChannel /* Starts a write request on the channel. */ int (*Write) (IWTSVirtualChannel* pChannel, uint32 cbSize, - char* pBuffer, + uint8* pBuffer, void* pReserved); /* Closes the channel. */ int (*Close) (IWTSVirtualChannel* pChannel); @@ -118,7 +118,7 @@ struct _IWTSListenerCallback the associated listener. */ int (*OnNewChannelConnection) (IWTSListenerCallback* pListenerCallback, IWTSVirtualChannel* pChannel, - char* Data, + uint8* Data, int* pbAccept, IWTSVirtualChannelCallback** ppCallback); }; From 6757c6ea59c6918e894c4aaee016c169b6d2eec0 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 19 Sep 2011 22:00:56 +0800 Subject: [PATCH 2/9] libfreerdp-utils/list: add list_peek(). --- include/freerdp/utils/list.h | 1 + libfreerdp-utils/list.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/include/freerdp/utils/list.h b/include/freerdp/utils/list.h index a121cdfcf..52c0279dc 100644 --- a/include/freerdp/utils/list.h +++ b/include/freerdp/utils/list.h @@ -43,6 +43,7 @@ FREERDP_API LIST* list_new(void); FREERDP_API void list_free(LIST* list); FREERDP_API void list_enqueue(LIST* list, void* data); FREERDP_API void* list_dequeue(LIST* list); +FREERDP_API void* list_peek(LIST* list); #define list_add(_l, _d) list_enqueue(_l, _d) FREERDP_API void* list_remove(LIST* list, void* data); diff --git a/libfreerdp-utils/list.c b/libfreerdp-utils/list.c index f5b1827bc..06f8bea16 100644 --- a/libfreerdp-utils/list.c +++ b/libfreerdp-utils/list.c @@ -97,6 +97,14 @@ void* list_dequeue(LIST* list) return data; } +void* list_peek(LIST* list) +{ + LIST_ITEM* item; + + item = list->head; + return item ? item->data : NULL; +} + void* list_remove(LIST* list, void* data) { LIST_ITEM* item; From 8682228ec485a718cbb55f0d9a8e2ff316eb9c3a Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 19 Sep 2011 22:52:16 +0800 Subject: [PATCH 3/9] libfreerdp-utils/thread: separate freerdp_thread_free() call. --- channels/rdpdr/disk/disk_main.c | 1 + channels/rdpdr/printer/printer_main.c | 1 + include/freerdp/utils/thread.h | 6 +++++- libfreerdp-utils/svc_plugin.c | 1 + libfreerdp-utils/thread.c | 5 +++++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/channels/rdpdr/disk/disk_main.c b/channels/rdpdr/disk/disk_main.c index 1bf546e08..d0496b1d3 100644 --- a/channels/rdpdr/disk/disk_main.c +++ b/channels/rdpdr/disk/disk_main.c @@ -555,6 +555,7 @@ static void disk_free(DEVICE* device) DISK_FILE* file; freerdp_thread_stop(disk->thread); + freerdp_thread_free(disk->thread); while ((irp = (IRP*)list_dequeue(disk->irp_list)) != NULL) irp->Discard(irp); diff --git a/channels/rdpdr/printer/printer_main.c b/channels/rdpdr/printer/printer_main.c index ef961e6bf..59cfbc548 100644 --- a/channels/rdpdr/printer/printer_main.c +++ b/channels/rdpdr/printer/printer_main.c @@ -210,6 +210,7 @@ static void printer_free(DEVICE* device) IRP* irp; freerdp_thread_stop(printer_dev->thread); + freerdp_thread_free(printer_dev->thread); while ((irp = (IRP*)list_dequeue(printer_dev->irp_list)) != NULL) irp->Discard(irp); diff --git a/include/freerdp/utils/thread.h b/include/freerdp/utils/thread.h index 4739c03a6..10fe682a6 100644 --- a/include/freerdp/utils/thread.h +++ b/include/freerdp/utils/thread.h @@ -43,11 +43,15 @@ struct _freerdp_thread FREERDP_API freerdp_thread* freerdp_thread_new(void); FREERDP_API void freerdp_thread_start(freerdp_thread* thread, void* func, void* arg); FREERDP_API void freerdp_thread_stop(freerdp_thread* thread); +FREERDP_API void freerdp_thread_free(freerdp_thread* thread); #define freerdp_thread_wait(_t) wait_obj_select(_t->signals, _t->num_signals, -1) #define freerdp_thread_wait_timeout(_t, _timeout) wait_obj_select(_t->signals, _t->num_signals, _timeout) #define freerdp_thread_is_stopped(_t) wait_obj_is_set(_t->signals[0]) -#define freerdp_thread_quit(_t) _t->status = -1 +#define freerdp_thread_is_running(_t) (_t->status == 1) +#define freerdp_thread_quit(_t) do { \ + _t->status = -1; \ + wait_obj_clear(_t->signals[0]); } while (0) #define freerdp_thread_signal(_t) wait_obj_set(_t->signals[1]) #define freerdp_thread_reset(_t) wait_obj_clear(_t->signals[1]) #define freerdp_thread_lock(_t) freerdp_mutex_lock(_t->mutex) diff --git a/libfreerdp-utils/svc_plugin.c b/libfreerdp-utils/svc_plugin.c index 3baf3881e..72379d57d 100644 --- a/libfreerdp-utils/svc_plugin.c +++ b/libfreerdp-utils/svc_plugin.c @@ -303,6 +303,7 @@ static void svc_plugin_process_terminated(rdpSvcPlugin* plugin) svc_data_in_item* item; freerdp_thread_stop(plugin->priv->thread); + freerdp_thread_free(plugin->priv->thread); plugin->channel_entry_points.pVirtualChannelClose(plugin->priv->open_handle); xfree(plugin->channel_entry_points.pExtendedData); diff --git a/libfreerdp-utils/thread.c b/libfreerdp-utils/thread.c index 3f95df1a8..49e7ff494 100644 --- a/libfreerdp-utils/thread.c +++ b/libfreerdp-utils/thread.c @@ -72,6 +72,11 @@ void freerdp_thread_stop(freerdp_thread* thread) i++; freerdp_usleep(100); } +} + +void freerdp_thread_free(freerdp_thread* thread) +{ + int i; for (i = 0; i < thread->num_signals; i++) wait_obj_free(thread->signals[i]); From fee5b3fab8e43474ed2bce3b8a3ef95e692580d2 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 19 Sep 2011 22:53:00 +0800 Subject: [PATCH 4/9] drdynvc: fix incorrect data length handling. --- channels/drdynvc/drdynvc_main.c | 73 +++++++++++++++------------------ 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/channels/drdynvc/drdynvc_main.c b/channels/drdynvc/drdynvc_main.c index 5fda6ac27..27ad531b7 100644 --- a/channels/drdynvc/drdynvc_main.c +++ b/channels/drdynvc/drdynvc_main.c @@ -150,20 +150,20 @@ int drdynvc_push_event(drdynvcPlugin* drdynvc, RDP_EVENT* event) return 0; } -static int drdynvc_process_capability_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in) +static int drdynvc_process_capability_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* s) { STREAM* data_out; int error; DEBUG_DVC("Sp=%d cbChId=%d", Sp, cbChId); - stream_seek(data_in, 1); /* pad */ - stream_read_uint16(data_in, drdynvc->version); + stream_seek(s, 1); /* pad */ + stream_read_uint16(s, drdynvc->version); if (drdynvc->version == 2) { - stream_read_uint16(data_in, drdynvc->PriorityCharge0); - stream_read_uint16(data_in, drdynvc->PriorityCharge1); - stream_read_uint16(data_in, drdynvc->PriorityCharge2); - stream_read_uint16(data_in, drdynvc->PriorityCharge3); + stream_read_uint16(s, drdynvc->PriorityCharge0); + stream_read_uint16(s, drdynvc->PriorityCharge1); + stream_read_uint16(s, drdynvc->PriorityCharge2); + stream_read_uint16(s, drdynvc->PriorityCharge3); } data_out = stream_new(4); stream_write_uint16(data_out, 0x0050); /* Cmd+Sp+cbChId+Pad. Note: MSTSC sends 0x005c */ @@ -196,23 +196,23 @@ static uint32 drdynvc_read_variable_uint(STREAM* stream, int cbLen) return val; } -static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in) +static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* s) { STREAM* data_out; int pos; int error; uint32 ChannelId; - ChannelId = drdynvc_read_variable_uint(data_in, cbChId); - pos = stream_get_pos(data_in); - DEBUG_DVC("ChannelId=%d ChannelName=%s", ChannelId, stream_get_tail(data_in)); + ChannelId = drdynvc_read_variable_uint(s, cbChId); + pos = stream_get_pos(s); + DEBUG_DVC("ChannelId=%d ChannelName=%s", ChannelId, stream_get_tail(s)); - error = dvcman_create_channel(drdynvc->channel_mgr, ChannelId, (char*)stream_get_tail(data_in)); + error = dvcman_create_channel(drdynvc->channel_mgr, ChannelId, (char*)stream_get_tail(s)); data_out = stream_new(pos + 4); stream_write_uint8(data_out, 0x10 | cbChId); - stream_set_pos(data_in, 1); - stream_copy(data_out, data_in, pos - 1); + stream_set_pos(s, 1); + stream_copy(data_out, s, pos - 1); if (error == 0) { @@ -234,16 +234,14 @@ static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cb return 0; } -static int drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in, int in_length) +static int drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* s) { - int pos; uint32 ChannelId; uint32 Length; int error; - ChannelId = drdynvc_read_variable_uint(data_in, cbChId); - Length = drdynvc_read_variable_uint(data_in, Sp); - pos = stream_get_pos(data_in); + ChannelId = drdynvc_read_variable_uint(s, cbChId); + Length = drdynvc_read_variable_uint(s, Sp); DEBUG_DVC("ChannelId=%d Length=%d", ChannelId, Length); error = dvcman_receive_channel_data_first(drdynvc->channel_mgr, ChannelId, Length); @@ -251,74 +249,69 @@ static int drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, int cbChId return error; return dvcman_receive_channel_data(drdynvc->channel_mgr, ChannelId, - stream_get_tail(data_in), in_length - pos); + stream_get_tail(s), stream_get_left(s)); } -static int drdynvc_process_data(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in, int in_length) +static int drdynvc_process_data(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* s) { - int pos; uint32 ChannelId; - ChannelId = drdynvc_read_variable_uint(data_in, cbChId); - pos = stream_get_pos(data_in); + ChannelId = drdynvc_read_variable_uint(s, cbChId); DEBUG_DVC("ChannelId=%d", ChannelId); return dvcman_receive_channel_data(drdynvc->channel_mgr, ChannelId, - stream_get_tail(data_in), in_length - pos); + stream_get_tail(s), stream_get_left(s)); } -static int drdynvc_process_close_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in) +static int drdynvc_process_close_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* s) { uint32 ChannelId; - ChannelId = drdynvc_read_variable_uint(data_in, cbChId); + ChannelId = drdynvc_read_variable_uint(s, cbChId); DEBUG_DVC("ChannelId=%d", ChannelId); dvcman_close_channel(drdynvc->channel_mgr, ChannelId); return 0; } -static void drdynvc_process_receive(rdpSvcPlugin* plugin, STREAM* data_in) +static void drdynvc_process_receive(rdpSvcPlugin* plugin, STREAM* s) { drdynvcPlugin* drdynvc = (drdynvcPlugin*)plugin; - int in_length; int value; int Cmd; int Sp; int cbChId; - in_length = stream_get_length(data_in); - stream_set_pos(data_in, 0); - - stream_read_uint8(data_in, value); + stream_read_uint8(s, value); Cmd = (value & 0xf0) >> 4; Sp = (value & 0x0c) >> 2; cbChId = (value & 0x03) >> 0; - DEBUG_DVC("in_length=%d Cmd=0x%x", in_length, Cmd); + + DEBUG_DVC("Cmd=0x%x", Cmd); switch (Cmd) { case CAPABILITY_REQUEST_PDU: - drdynvc_process_capability_request(drdynvc, Sp, cbChId, data_in); + drdynvc_process_capability_request(drdynvc, Sp, cbChId, s); break; case CREATE_REQUEST_PDU: - drdynvc_process_create_request(drdynvc, Sp, cbChId, data_in); + drdynvc_process_create_request(drdynvc, Sp, cbChId, s); break; case DATA_FIRST_PDU: - drdynvc_process_data_first(drdynvc, Sp, cbChId, data_in, in_length); + drdynvc_process_data_first(drdynvc, Sp, cbChId, s); break; case DATA_PDU: - drdynvc_process_data(drdynvc, Sp, cbChId, data_in, in_length); + drdynvc_process_data(drdynvc, Sp, cbChId, s); break; case CLOSE_REQUEST_PDU: - drdynvc_process_close_request(drdynvc, Sp, cbChId, data_in); + drdynvc_process_close_request(drdynvc, Sp, cbChId, s); break; default: DEBUG_WARN("unknown drdynvc cmd 0x%x", Cmd); break; } - stream_free(data_in); + stream_free(s); } static void drdynvc_process_connect(rdpSvcPlugin* plugin) From dadb94a1e343648503949094a50053d81212a153 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Mon, 19 Sep 2011 22:54:09 +0800 Subject: [PATCH 5/9] tsmf: add tsmf main module. --- channels/drdynvc/CMakeLists.txt | 3 + channels/drdynvc/tsmf/CMakeLists.txt | 45 ++ channels/drdynvc/tsmf/tsmf_audio.c | 80 +++ channels/drdynvc/tsmf/tsmf_audio.h | 49 ++ channels/drdynvc/tsmf/tsmf_codec.c | 407 +++++++++++++ channels/drdynvc/tsmf/tsmf_codec.h | 29 + channels/drdynvc/tsmf/tsmf_constants.h | 120 ++++ channels/drdynvc/tsmf/tsmf_decoder.c | 81 +++ channels/drdynvc/tsmf/tsmf_decoder.h | 50 ++ channels/drdynvc/tsmf/tsmf_ifman.c | 478 +++++++++++++++ channels/drdynvc/tsmf/tsmf_ifman.h | 65 ++ channels/drdynvc/tsmf/tsmf_main.c | 443 ++++++++++++++ channels/drdynvc/tsmf/tsmf_main.h | 29 + channels/drdynvc/tsmf/tsmf_media.c | 795 +++++++++++++++++++++++++ channels/drdynvc/tsmf/tsmf_media.h | 59 ++ channels/drdynvc/tsmf/tsmf_types.h | 47 ++ 16 files changed, 2780 insertions(+) create mode 100644 channels/drdynvc/tsmf/CMakeLists.txt create mode 100644 channels/drdynvc/tsmf/tsmf_audio.c create mode 100644 channels/drdynvc/tsmf/tsmf_audio.h create mode 100644 channels/drdynvc/tsmf/tsmf_codec.c create mode 100644 channels/drdynvc/tsmf/tsmf_codec.h create mode 100644 channels/drdynvc/tsmf/tsmf_constants.h create mode 100644 channels/drdynvc/tsmf/tsmf_decoder.c create mode 100644 channels/drdynvc/tsmf/tsmf_decoder.h create mode 100644 channels/drdynvc/tsmf/tsmf_ifman.c create mode 100644 channels/drdynvc/tsmf/tsmf_ifman.h create mode 100644 channels/drdynvc/tsmf/tsmf_main.c create mode 100644 channels/drdynvc/tsmf/tsmf_main.h create mode 100644 channels/drdynvc/tsmf/tsmf_media.c create mode 100644 channels/drdynvc/tsmf/tsmf_media.h create mode 100644 channels/drdynvc/tsmf/tsmf_types.h diff --git a/channels/drdynvc/CMakeLists.txt b/channels/drdynvc/CMakeLists.txt index c3bdbb0b4..ce32efd1b 100644 --- a/channels/drdynvc/CMakeLists.txt +++ b/channels/drdynvc/CMakeLists.txt @@ -31,3 +31,6 @@ set_target_properties(drdynvc PROPERTIES PREFIX "") target_link_libraries(drdynvc freerdp-utils) install(TARGETS drdynvc DESTINATION ${FREERDP_PLUGIN_PATH}) + +add_subdirectory(tsmf) + diff --git a/channels/drdynvc/tsmf/CMakeLists.txt b/channels/drdynvc/tsmf/CMakeLists.txt new file mode 100644 index 000000000..98e25058d --- /dev/null +++ b/channels/drdynvc/tsmf/CMakeLists.txt @@ -0,0 +1,45 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(TSMF_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 +) + +include_directories(..) + +add_library(tsmf ${TSMF_SRCS}) +set_target_properties(tsmf PROPERTIES PREFIX "") + +target_link_libraries(tsmf freerdp-utils) + +install(TARGETS tsmf DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/tsmf_audio.c b/channels/drdynvc/tsmf/tsmf_audio.c new file mode 100644 index 000000000..5a1a14512 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_audio.c @@ -0,0 +1,80 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 +#include +#include +#include +#include + +#include "tsmf_audio.h" + +static ITSMFAudioDevice* tsmf_load_audio_device_by_name(const char* name, const char* device) +{ + ITSMFAudioDevice* audio; + TSMF_AUDIO_DEVICE_ENTRY entry; + char* fullname; + + if (strrchr(name, '.') != NULL) + entry = (TSMF_AUDIO_DEVICE_ENTRY) freerdp_load_plugin(name, TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME); + else + { + fullname = xzalloc(strlen(name) + 6); + strcpy(fullname, "tsmf_"); + strcat(fullname, name); + entry = (TSMF_AUDIO_DEVICE_ENTRY) freerdp_load_plugin(fullname, TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME); + xfree(fullname); + } + if (entry == NULL) + { + return NULL; + } + + audio = entry(); + if (audio == NULL) + { + DEBUG_WARN("failed to call export function in %s", name); + return NULL; + } + if (!audio->Open(audio, device)) + { + audio->Free(audio); + audio = NULL; + } + return audio; +} + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device) +{ + ITSMFAudioDevice* audio; + + if (name) + { + audio = tsmf_load_audio_device_by_name(name, device); + } + else + { + audio = tsmf_load_audio_device_by_name("pulse", device); + if (!audio) + audio = tsmf_load_audio_device_by_name("alsa", device); + } + + return audio; +} + diff --git a/channels/drdynvc/tsmf/tsmf_audio.h b/channels/drdynvc/tsmf/tsmf_audio.h new file mode 100644 index 000000000..af0075927 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_audio.h @@ -0,0 +1,49 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 __TSMF_AUDIO_H +#define __TSMF_AUDIO_H + +#include "drdynvc_types.h" + +typedef struct _ITSMFAudioDevice ITSMFAudioDevice; + +struct _ITSMFAudioDevice +{ + /* Open the audio device. */ + boolean (*Open) (ITSMFAudioDevice* audio, const char* device); + /* Set the audio data format. */ + boolean (*SetFormat) (ITSMFAudioDevice* audio, uint32 sample_rate, uint32 channels, uint32 bits_per_sample); + /* Play audio data. */ + boolean (*Play) (ITSMFAudioDevice* audio, uint8* data, uint32 data_size); + /* Get the latency of the last written sample, in 100ns */ + uint64 (*GetLatency) (ITSMFAudioDevice* audio); + /* Flush queued audio data */ + void (*Flush) (ITSMFAudioDevice* audio); + /* Free the audio device */ + void (*Free) (ITSMFAudioDevice* audio); +}; + +#define TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME "TSMFAudioDeviceEntry" +typedef ITSMFAudioDevice* (*TSMF_AUDIO_DEVICE_ENTRY) (void); + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device); + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_codec.c b/channels/drdynvc/tsmf/tsmf_codec.c new file mode 100644 index 000000000..5a5134c6b --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_codec.c @@ -0,0 +1,407 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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. + */ + +#include +#include +#include +#include +#include + +#include "drdynvc_types.h" +#include "tsmf_constants.h" +#include "tsmf_types.h" + +#include "tsmf_codec.h" + +typedef struct _TSMFMediaTypeMap +{ + uint8 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 + }, + + /* 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 + }, + + /* 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 + }, + + /* 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 + }, + + { + { 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 + }, + + { + { 0 }, + "Unknown", + TSMF_FORMAT_TYPE_UNKNOWN + } +}; + +static void tsmf_print_guid(const uint8* guid) +{ +#ifdef WITH_DEBUG_DVC + int i; + + for (i = 3; i >= 0; i--) + printf("%02X", guid[i]); + printf("-"); + for (i = 5; i >= 4; i--) + printf("%02X", guid[i]); + printf("-"); + for (i = 7; i >= 6; i--) + printf("%02X", guid[i]); + printf("-"); + for (i = 8; i < 16; i++) + { + printf("%02X", guid[i]); + if (i == 9) + printf("-"); + } + printf("\n"); +#endif +} + +/* http://msdn.microsoft.com/en-us/library/dd318229.aspx */ +static uint32 tsmf_codec_parse_BITMAPINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, STREAM* s, boolean bypass) +{ + uint32 biSize; + uint32 biWidth; + uint32 biHeight; + + 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 (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, STREAM* s) +{ + uint64 AvgTimePerFrame; + + /* VIDEOINFOHEADER2.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); + /* VIDEOINFOHEADER2.rcTarget */ + stream_seek(s, 16); + /* VIDEOINFOHEADER2.dwBitRate */ + stream_read_uint32(s, mediatype->BitRate); + /* VIDEOINFOHEADER2.dwBitErrorRate */ + stream_seek_uint32(s); + /* VIDEOINFOHEADER2.AvgTimePerFrame */ + stream_read_uint64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL); + /* Remaining fields before bmiHeader */ + stream_seek(s, 24); + + return 72; +} + +boolean tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, STREAM* s) +{ + int i; + uint32 cbFormat; + boolean ret = True; + + memset(mediatype, 0, sizeof(TS_AM_MEDIA_TYPE)); + + /* MajorType */ + DEBUG_DVC("MajorType:"); + tsmf_print_guid(stream_get_tail(s)); + for (i = 0; tsmf_major_type_map[i].type != TSMF_MAJOR_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_major_type_map[i].guid, stream_get_tail(s), 16) == 0) + break; + } + mediatype->MajorType = tsmf_major_type_map[i].type; + if (mediatype->MajorType == TSMF_MAJOR_TYPE_UNKNOWN) + ret = False; + DEBUG_DVC("MajorType %s", tsmf_major_type_map[i].name); + stream_seek(s, 16); + + /* SubType */ + DEBUG_DVC("SubType:"); + tsmf_print_guid(stream_get_tail(s)); + for (i = 0; tsmf_sub_type_map[i].type != TSMF_SUB_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_sub_type_map[i].guid, stream_get_tail(s), 16) == 0) + break; + } + mediatype->SubType = tsmf_sub_type_map[i].type; + if (mediatype->SubType == TSMF_SUB_TYPE_UNKNOWN) + ret = False; + DEBUG_DVC("SubType %s", tsmf_sub_type_map[i].name); + stream_seek(s, 16); + + /* bFixedSizeSamples, bTemporalCompression, SampleSize */ + stream_seek(s, 12); + + /* FormatType */ + DEBUG_DVC("FormatType:"); + tsmf_print_guid(stream_get_tail(s)); + for (i = 0; tsmf_format_type_map[i].type != TSMF_FORMAT_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_format_type_map[i].guid, stream_get_tail(s), 16) == 0) + break; + } + mediatype->FormatType = tsmf_format_type_map[i].type; + if (mediatype->FormatType == TSMF_FORMAT_TYPE_UNKNOWN) + ret = False; + DEBUG_DVC("FormatType %s", tsmf_format_type_map[i].name); + stream_seek(s, 16); + + /* cbFormat */ + stream_read_uint32(s, cbFormat); + DEBUG_DVC("cbFormat %d", cbFormat); + +#ifdef WITH_DEBUG_DVC + freerdp_hexdump(stream_get_tail(s), cbFormat); +#endif + + switch (mediatype->FormatType) + { + case TSMF_FORMAT_TYPE_MFVIDEOFORMAT: + /* http://msdn.microsoft.com/en-us/library/aa473808.aspx */ + + 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) + { + mediatype->ExtraDataSize = cbFormat - 176; + mediatype->ExtraData = stream_get_tail(s); + } + break; + + case TSMF_FORMAT_TYPE_WAVEFORMATEX: + /* http://msdn.microsoft.com/en-us/library/dd757720.aspx */ + + 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) + mediatype->ExtraData = stream_get_tail(s); + + break; + + case TSMF_FORMAT_TYPE_MPEG2VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390707.aspx */ + + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + i += tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, True); + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + mediatype->ExtraData = stream_get_tail(s); + } + break; + + case TSMF_FORMAT_TYPE_VIDEOINFO2: + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + i += tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, False); + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + mediatype->ExtraData = stream_get_tail(s); + } + break; + + default: + break; + } + + if (mediatype->SamplesPerSecond.Numerator == 0) + mediatype->SamplesPerSecond.Numerator = 1; + if (mediatype->SamplesPerSecond.Denominator == 0) + mediatype->SamplesPerSecond.Denominator = 1; + + return ret; +} + +boolean tsmf_codec_check_media_type(STREAM* s) +{ + uint8* m; + boolean ret; + TS_AM_MEDIA_TYPE mediatype; + + stream_get_mark(s, m); + ret = tsmf_codec_parse_media_type(&mediatype, s); + stream_set_mark(s, m); + + return ret; +} + diff --git a/channels/drdynvc/tsmf/tsmf_codec.h b/channels/drdynvc/tsmf/tsmf_codec.h new file mode 100644 index 000000000..eb6bbeaf5 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_codec.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 __TSMF_CODEC +#define __TSMF_CODEC + +#include "tsmf_types.h" + +boolean tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, STREAM* s); +boolean tsmf_codec_check_media_type(STREAM* s); + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_constants.h b/channels/drdynvc/tsmf/tsmf_constants.h new file mode 100644 index 000000000..e836dd72f --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_constants.h @@ -0,0 +1,120 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - Constants + * + * 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 __TSMF_CONSTANTS_H +#define __TSMF_CONSTANTS_H + +#define GUID_SIZE 16 +#define TSMF_BUFFER_PADDING_SIZE 8 + +/* Interface IDs defined in [MS-RDPEV]. There's no constant names in the MS + documentation, so we create them on our own. */ +#define TSMF_INTERFACE_DEFAULT 0x00000000 +#define TSMF_INTERFACE_CLIENT_NOTIFICATIONS 0x00000001 +#define TSMF_INTERFACE_CAPABILITIES 0x00000002 + +/* Interface ID Mask */ +#define STREAM_ID_STUB 0x80000000 +#define STREAM_ID_PROXY 0x40000000 +#define STREAM_ID_NONE 0x00000000 + +/* Functon ID */ +/* Common IDs for all interfaces are as follows. */ +#define RIMCALL_RELEASE 0x00000001 +#define RIMCALL_QUERYINTERFACE 0x00000002 +/* Capabilities Negotiator Interface IDs are as follows. */ +#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100 +/* The Client Notifications Interface ID is as follows. */ +#define PLAYBACK_ACK 0x00000100 +#define CLIENT_EVENT_NOTIFICATION 0x00000101 +/* Server Data Interface IDs are as follows. */ +#define EXCHANGE_CAPABILITIES_REQ 0x00000100 +#define SET_CHANNEL_PARAMS 0x00000101 +#define ADD_STREAM 0x00000102 +#define ON_SAMPLE 0x00000103 +#define SET_VIDEO_WINDOW 0x00000104 +#define ON_NEW_PRESENTATION 0x00000105 +#define SHUTDOWN_PRESENTATION_REQ 0x00000106 +#define SET_TOPOLOGY_REQ 0x00000107 +#define CHECK_FORMAT_SUPPORT_REQ 0x00000108 +#define ON_PLAYBACK_STARTED 0x00000109 +#define ON_PLAYBACK_PAUSED 0x0000010a +#define ON_PLAYBACK_STOPPED 0x0000010b +#define ON_PLAYBACK_RESTARTED 0x0000010c +#define ON_PLAYBACK_RATE_CHANGED 0x0000010d +#define ON_FLUSH 0x0000010e +#define ON_STREAM_VOLUME 0x0000010f +#define ON_CHANNEL_VOLUME 0x00000110 +#define ON_END_OF_STREAM 0x00000111 +#define SET_ALLOCATOR 0x00000112 +#define NOTIFY_PREROLL 0x00000113 +#define UPDATE_GEOMETRY_INFO 0x00000114 +#define REMOVE_STREAM 0x00000115 + +/* 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 + +/* 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 + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_decoder.c b/channels/drdynvc/tsmf/tsmf_decoder.c new file mode 100644 index 000000000..db31c027b --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_decoder.c @@ -0,0 +1,81 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 +#include +#include +#include +#include + +#include "drdynvc_types.h" +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +static ITSMFDecoder* tsmf_load_decoder_by_name(const char* name, TS_AM_MEDIA_TYPE* media_type) +{ + ITSMFDecoder* decoder; + TSMF_DECODER_ENTRY entry; + char* fullname; + + if (strrchr(name, '.') != NULL) + entry = (TSMF_DECODER_ENTRY) freerdp_load_plugin(name, TSMF_DECODER_EXPORT_FUNC_NAME); + else + { + fullname = xzalloc(strlen(name) + 6); + strcpy(fullname, "tsmf_"); + strcat(fullname, name); + entry = (TSMF_DECODER_ENTRY) freerdp_load_plugin(fullname, TSMF_DECODER_EXPORT_FUNC_NAME); + xfree(fullname); + } + if (entry == NULL) + { + return NULL; + } + + decoder = entry(); + if (decoder == NULL) + { + DEBUG_WARN("failed to call export function in %s", name); + return NULL; + } + if (!decoder->SetFormat(decoder, media_type)) + { + decoder->Free(decoder); + decoder = NULL; + } + return decoder; +} + +ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type) +{ + ITSMFDecoder* decoder; + + if (name) + { + decoder = tsmf_load_decoder_by_name(name, media_type); + } + else + { + decoder = tsmf_load_decoder_by_name("ffmpeg", media_type); + } + + return decoder; +} + diff --git a/channels/drdynvc/tsmf/tsmf_decoder.h b/channels/drdynvc/tsmf/tsmf_decoder.h new file mode 100644 index 000000000..b79c263aa --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_decoder.h @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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. + */ + +#ifndef __TSMF_DECODER_H +#define __TSMF_DECODER_H + +#include "drdynvc_types.h" +#include "tsmf_types.h" + +typedef struct _ITSMFDecoder ITSMFDecoder; + +struct _ITSMFDecoder +{ + /* Set the decoder format. Return True if supported. */ + boolean (*SetFormat) (ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type); + /* Decode a sample. */ + boolean (*Decode) (ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions); + /* Get the decoded data */ + uint8* (*GetDecodedData) (ITSMFDecoder* decoder, uint32* size); + /* Get the pixel format of decoded video frame */ + uint32 (*GetDecodedFormat) (ITSMFDecoder* decoder); + /* Get the width and height of decoded video frame */ + boolean (*GetDecodedDimension) (ITSMFDecoder* decoder, uint32* width, uint32* height); + /* Free the decoder */ + void (*Free) (ITSMFDecoder* decoder); +}; + +#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry" +typedef ITSMFDecoder* (*TSMF_DECODER_ENTRY) (void); + +ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type); + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_ifman.c b/channels/drdynvc/tsmf/tsmf_ifman.c new file mode 100644 index 000000000..fb2ea203c --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_ifman.c @@ -0,0 +1,478 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - Interface Manipulation + * + * 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 +#include +#include +#include +#include + +#include "drdynvc_types.h" +#include "tsmf_constants.h" +#include "tsmf_media.h" +#include "tsmf_codec.h" + +#include "tsmf_ifman.h" + +int tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman) +{ + uint32 CapabilityValue; + + stream_read_uint32(ifman->input, CapabilityValue); + DEBUG_DVC("server CapabilityValue %d", CapabilityValue); + + stream_check_size(ifman->output, 8); + stream_write_uint32(ifman->output, 1); /* CapabilityValue */ + stream_write_uint32(ifman->output, 0); /* Result */ + + return 0; +} + +int tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman) +{ + uint32 i; + uint32 v; + uint32 pos; + uint32 CapabilityType; + uint32 cbCapabilityLength; + uint32 numHostCapabilities; + + pos = stream_get_pos(ifman->output); + stream_check_size(ifman->output, ifman->input_size + 4); + stream_copy(ifman->output, ifman->input, ifman->input_size); + + stream_set_pos(ifman->output, pos); + stream_read_uint32(ifman->output, numHostCapabilities); + for (i = 0; i < numHostCapabilities; i++) + { + stream_read_uint32(ifman->output, CapabilityType); + stream_read_uint32(ifman->output, cbCapabilityLength); + pos = stream_get_pos(ifman->output); + switch (CapabilityType) + { + case 1: /* Protocol version request */ + stream_read_uint32(ifman->output, v); + DEBUG_DVC("server protocol version %d", v); + break; + case 2: /* Supported platform */ + stream_peek_uint32(ifman->output, v); + DEBUG_DVC("server supported platform %d", 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: + DEBUG_WARN("unknown capability type %d", CapabilityType); + break; + } + stream_set_pos(ifman->output, pos + cbCapabilityLength); + } + stream_write_uint32(ifman->output, 0); /* Result */ + + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + + return 0; +} + +int tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman) +{ + uint32 numMediaType; + uint32 PlatformCookie; + uint32 FormatSupported = 1; + + stream_read_uint32(ifman->input, PlatformCookie); + stream_seek_uint32(ifman->input); /* NoRolloverFlags (4 bytes) */ + stream_read_uint32(ifman->input, numMediaType); + + DEBUG_DVC("PlatformCookie %d numMediaType %d", PlatformCookie, numMediaType); + + if (!tsmf_codec_check_media_type(ifman->input)) + FormatSupported = 0; + + if (FormatSupported) + DEBUG_DVC("format ok."); + + stream_check_size(ifman->output, 12); + 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 0; +} + +int tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman) +{ + int error = 0; + TSMF_PRESENTATION* presentation; + + DEBUG_DVC(""); + + presentation = tsmf_presentation_new(stream_get_tail(ifman->input), ifman->channel_callback); + if (presentation == NULL) + error = 1; + tsmf_presentation_set_audio_device(presentation, ifman->audio_name, ifman->audio_device); + ifman->output_pending = True; + return error; +} + +int tsmf_ifman_add_stream(TSMF_IFMAN* ifman) +{ + uint32 StreamId; + int error = 0; + TSMF_STREAM* stream; + TSMF_PRESENTATION* presentation; + + DEBUG_DVC(""); + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + stream_seek(ifman->input, 16); + + if (presentation == NULL) + error = 1; + else + { + stream_read_uint32(ifman->input, StreamId); + stream_seek_uint32(ifman->input); /* numMediaType */ + stream = tsmf_stream_new(presentation, StreamId); + if (stream) + tsmf_stream_set_format(stream, ifman->decoder_name, ifman->input); + } + ifman->output_pending = True; + return error; +} + +int tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + + stream_check_size(ifman->output, 8); + 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 0; +} + +int tsmf_ifman_remove_stream(TSMF_IFMAN* ifman) +{ + int error = 0; + uint32 StreamId; + TSMF_STREAM* stream; + TSMF_PRESENTATION* presentation; + + DEBUG_DVC(""); + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + stream_seek(ifman->input, 16); + + if (presentation == NULL) + error = 1; + else + { + stream_read_uint32(ifman->input, StreamId); + stream = tsmf_stream_find_by_id(presentation, StreamId); + if (stream) + tsmf_stream_free(stream); + else + error = 1; + } + ifman->output_pending = True; + return error; +} + +int tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + + DEBUG_DVC(""); + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + if (presentation) + tsmf_presentation_free(presentation); + + stream_check_size(ifman->output, 4); + stream_write_uint32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return 0; +} + +int tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_set_video_window(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + uint32 numGeometryInfo; + uint32 Left; + uint32 Top; + uint32 Width; + uint32 Height; + uint32 cbVisibleRect; + RDP_RECT* rects = NULL; + int num_rects = 0; + int error = 0; + int i; + int pos; + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + stream_seek(ifman->input, 16); + + stream_read_uint32(ifman->input, numGeometryInfo); + pos = stream_get_pos(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_set_pos(ifman->input, pos + numGeometryInfo); + stream_read_uint32(ifman->input, cbVisibleRect); + num_rects = cbVisibleRect / 16; + + DEBUG_DVC("numGeometryInfo %d Width %d Height %d Left %d Top %d cbVisibleRect %d num_rects %d", + numGeometryInfo, Width, Height, Left, Top, cbVisibleRect, num_rects); + + if (presentation == NULL) + error = 1; + else + { + if (num_rects > 0) + { + rects = (RDP_RECT*) xzalloc(sizeof(RDP_RECT) * num_rects); + for (i = 0; i < num_rects; i++) + { + stream_read_uint16(ifman->input, rects[i].y); /* Top */ + stream_seek_uint16(ifman->input); + stream_read_uint16(ifman->input, rects[i].x); /* Left */ + stream_seek_uint16(ifman->input); + stream_read_uint16(ifman->input, rects[i].height); /* Bottom */ + stream_seek_uint16(ifman->input); + stream_read_uint16(ifman->input, rects[i].width); /* Right */ + stream_seek_uint16(ifman->input); + rects[i].width -= rects[i].x; + rects[i].height -= rects[i].y; + + DEBUG_DVC("rect %d: %d %d %d %d", i, + rects[i].x, rects[i].y, rects[i].width, rects[i].height); + } + } + tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height, num_rects, rects); + } + ifman->output_pending = True; + return error; +} + +int tsmf_ifman_set_allocator(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_on_sample(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + TSMF_STREAM* stream; + uint32 StreamId; + uint64 SampleStartTime; + uint64 SampleEndTime; + uint64 ThrottleDuration; + uint32 SampleExtensions; + uint32 cbData; + + 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); + + DEBUG_DVC("MessageId %d StreamId %d SampleStartTime %d SampleEndTime %d " + "ThrottleDuration %d SampleExtensions %d cbData %d", + ifman->message_id, StreamId, (int)SampleStartTime, (int)SampleEndTime, + (int)ThrottleDuration, SampleExtensions, cbData); + + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + if (presentation == NULL) + { + DEBUG_WARN("unknown presentation id"); + return 1; + } + stream = tsmf_stream_find_by_id(presentation, StreamId); + if (stream == NULL) + { + DEBUG_WARN("unknown stream id"); + return 1; + } + tsmf_stream_push_sample(stream, ifman->channel_callback, + ifman->message_id, SampleStartTime, SampleEndTime, ThrottleDuration, SampleExtensions, + cbData, stream_get_tail(ifman->input)); + + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_on_flush(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + uint32 StreamId; + + stream_seek(ifman->input, 16); + stream_read_uint32(ifman->input, StreamId); + DEBUG_DVC("StreamId %d", StreamId); + + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + if (presentation == NULL) + { + DEBUG_WARN("unknown presentation id"); + return 1; + } + + tsmf_presentation_flush(presentation); + + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + TSMF_STREAM* stream; + uint32 StreamId; + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + stream_seek(ifman->input, 16); + stream_read_uint32(ifman->input, StreamId); + stream = tsmf_stream_find_by_id(presentation, StreamId); + tsmf_stream_end(stream); + + DEBUG_DVC("StreamId %d", StreamId); + + stream_check_size(ifman->output, 16); + stream_write_uint32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + stream_write_uint32(ifman->output, StreamId); /* StreamId */ + stream_write_uint32(ifman->output, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */ + stream_write_uint32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + + return 0; +} + +int tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + + DEBUG_DVC(""); + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + if (presentation) + tsmf_presentation_start(presentation); + else + DEBUG_WARN("unknown presentation id"); + + stream_check_size(ifman->output, 16); + 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 0; +} + +int tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman) +{ + DEBUG_DVC(""); + ifman->output_pending = True; + return 0; +} + +int tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + + DEBUG_DVC(""); + + presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input)); + if (presentation) + tsmf_presentation_stop(presentation); + else + DEBUG_WARN("unknown presentation id"); + + stream_check_size(ifman->output, 16); + 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 0; +} + +int tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN * ifman) +{ + DEBUG_DVC(""); + + stream_check_size(ifman->output, 16); + 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 0; +} + diff --git a/channels/drdynvc/tsmf/tsmf_ifman.h b/channels/drdynvc/tsmf/tsmf_ifman.h new file mode 100644 index 000000000..7a68c06b7 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_ifman.h @@ -0,0 +1,65 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - Interface Manipulation + * + * 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 __TSMF_IFMAN_H +#define __TSMF_IFMAN_H + +typedef struct _TSMF_IFMAN TSMF_IFMAN; +struct _TSMF_IFMAN +{ + IWTSVirtualChannelCallback* channel_callback; + const char* decoder_name; + const char* audio_name; + const char* audio_device; + uint8 presentation_id[16]; + uint32 stream_id; + uint32 message_id; + + STREAM* input; + uint32 input_size; + STREAM* output; + boolean output_pending; + uint32 output_interface_id; +}; + +int tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman); +int tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman); +int tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman); +int tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman); +int tsmf_ifman_add_stream(TSMF_IFMAN* ifman); +int tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman); +int tsmf_ifman_remove_stream(TSMF_IFMAN* ifman); +int tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman); +int tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman); +int tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman); +int tsmf_ifman_set_video_window(TSMF_IFMAN* ifman); +int tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman); +int tsmf_ifman_set_allocator(TSMF_IFMAN* ifman); +int tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman); +int tsmf_ifman_on_sample(TSMF_IFMAN* ifman); +int tsmf_ifman_on_flush(TSMF_IFMAN* ifman); +int tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman); +int tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman); +int tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman); +int tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman); +int tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman); +int tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman); + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_main.c b/channels/drdynvc/tsmf/tsmf_main.c new file mode 100644 index 000000000..7156dd212 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_main.c @@ -0,0 +1,443 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel + * + * 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 +#include +#include +#include +#include + +#include "drdynvc_types.h" +#include "tsmf_constants.h" +#include "tsmf_ifman.h" +#include "tsmf_media.h" + +#include "tsmf_main.h" + +typedef struct _TSMF_LISTENER_CALLBACK TSMF_LISTENER_CALLBACK; + +typedef struct _TSMF_CHANNEL_CALLBACK TSMF_CHANNEL_CALLBACK; + +typedef struct _TSMF_PLUGIN TSMF_PLUGIN; + +struct _TSMF_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +}; + +struct _TSMF_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + uint8 presentation_id[16]; + uint32 stream_id; +}; + +struct _TSMF_PLUGIN +{ + IWTSPlugin iface; + + TSMF_LISTENER_CALLBACK* listener_callback; + + const char* decoder_name; + const char* audio_name; + const char* audio_device; +}; + +void tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, + uint32 message_id, uint64 duration, uint32 data_size) +{ + STREAM* s; + int error; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + + s = stream_new(32); + stream_write_uint32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + stream_write_uint32(s, message_id); + stream_write_uint32(s, PLAYBACK_ACK); /* FunctionId */ + stream_write_uint32(s, callback->stream_id); /* StreamId */ + stream_write_uint64(s, duration); /* DataDuration */ + stream_write_uint64(s, data_size); /* cbData */ + + DEBUG_DVC("response size %d", stream_get_length(s)); + error = callback->channel->Write(callback->channel, stream_get_length(s), stream_get_head(s), NULL); + if (error) + { + DEBUG_WARN("response error %d", error); + } + stream_free(s); +} + +boolean tsmf_push_event(IWTSVirtualChannelCallback* pChannelCallback, + RDP_EVENT* event) +{ + int error; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + + error = callback->channel_mgr->PushEvent(callback->channel_mgr, event); + if (error) + { + DEBUG_WARN("response error %d", error); + return False; + } + return True; +} + +static int tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, + uint32 cbSize, + uint8* pBuffer) +{ + int length; + STREAM* input; + STREAM* output; + int error = -1; + TSMF_IFMAN ifman; + uint32 MessageId; + uint32 FunctionId; + uint32 InterfaceId; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + + /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */ + if (cbSize < 12) + { + DEBUG_WARN("invalid size. cbSize=%d", cbSize); + return 1; + } + input = stream_new(0); + stream_attach(input, (uint8*) pBuffer, cbSize); + output = stream_new(256); + stream_seek(output, 8); + + stream_read_uint32(input, InterfaceId); + stream_read_uint32(input, MessageId); + stream_read_uint32(input, FunctionId); + DEBUG_DVC("cbSize=%d InterfaceId=0x%X MessageId=0x%X FunctionId=0x%X", + cbSize, InterfaceId, MessageId, FunctionId); + + memset(&ifman, 0, sizeof(TSMF_IFMAN)); + 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; + memcpy(ifman.presentation_id, callback->presentation_id, 16); + ifman.stream_id = callback->stream_id; + ifman.message_id = MessageId; + ifman.input = input; + ifman.input_size = cbSize - 12; + ifman.output = output; + ifman.output_pending = False; + ifman.output_interface_id = InterfaceId; + + switch (InterfaceId) + { + case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE: + + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + error = tsmf_ifman_rim_exchange_capability_request(&ifman); + break; + + default: + break; + } + break; + + case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY: + + switch (FunctionId) + { + case SET_CHANNEL_PARAMS: + memcpy(callback->presentation_id, stream_get_tail(input), 16); + stream_seek(input, 16); + stream_read_uint32(input, callback->stream_id); + DEBUG_DVC("SET_CHANNEL_PARAMS StreamId=%d", callback->stream_id); + ifman.output_pending = True; + error = 0; + break; + + case EXCHANGE_CAPABILITIES_REQ: + error = tsmf_ifman_exchange_capability_request(&ifman); + break; + + case CHECK_FORMAT_SUPPORT_REQ: + error = tsmf_ifman_check_format_support_request(&ifman); + break; + + case ON_NEW_PRESENTATION: + error = tsmf_ifman_on_new_presentation(&ifman); + break; + + case ADD_STREAM: + error = tsmf_ifman_add_stream(&ifman); + break; + + case SET_TOPOLOGY_REQ: + error = tsmf_ifman_set_topology_request(&ifman); + break; + + case REMOVE_STREAM: + error = tsmf_ifman_remove_stream(&ifman); + break; + + case SHUTDOWN_PRESENTATION_REQ: + error = tsmf_ifman_shutdown_presentation(&ifman); + break; + + case ON_STREAM_VOLUME: + error = tsmf_ifman_on_stream_volume(&ifman); + break; + + case ON_CHANNEL_VOLUME: + error = tsmf_ifman_on_channel_volume(&ifman); + break; + + case SET_VIDEO_WINDOW: + error = tsmf_ifman_set_video_window(&ifman); + break; + + case UPDATE_GEOMETRY_INFO: + error = tsmf_ifman_update_geometry_info(&ifman); + break; + + case SET_ALLOCATOR: + error = tsmf_ifman_set_allocator(&ifman); + break; + + case NOTIFY_PREROLL: + error = tsmf_ifman_notify_preroll(&ifman); + break; + + case ON_SAMPLE: + error = tsmf_ifman_on_sample(&ifman); + break; + + case ON_FLUSH: + error = tsmf_ifman_on_flush(&ifman); + break; + + case ON_END_OF_STREAM: + error = tsmf_ifman_on_end_of_stream(&ifman); + break; + + case ON_PLAYBACK_STARTED: + error = tsmf_ifman_on_playback_started(&ifman); + break; + + case ON_PLAYBACK_PAUSED: + error = tsmf_ifman_on_playback_paused(&ifman); + break; + + case ON_PLAYBACK_RESTARTED: + error = tsmf_ifman_on_playback_restarted(&ifman); + break; + + case ON_PLAYBACK_STOPPED: + error = tsmf_ifman_on_playback_stopped(&ifman); + break; + + case ON_PLAYBACK_RATE_CHANGED: + error = tsmf_ifman_on_playback_rate_changed(&ifman); + break; + + default: + break; + } + break; + + default: + break; + } + + stream_detach(input); + stream_free(input); + input = NULL; + ifman.input = NULL; + + if (error == -1) + { + switch (FunctionId) + { + case RIMCALL_RELEASE: + /* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE) + This message does not require a reply. */ + error = 0; + 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. */ + error = 0; + break; + } + + if (error == -1) + { + DEBUG_WARN("InterfaceId 0x%X FunctionId 0x%X not processed.", + InterfaceId, FunctionId); + /* When a request is not implemented we return empty response indicating error */ + } + error = 0; + } + + if (error == 0 && !ifman.output_pending) + { + /* Response packet does not have FunctionId */ + length = stream_get_length(output); + stream_set_pos(output, 0); + stream_write_uint32(output, ifman.output_interface_id); + stream_write_uint32(output, MessageId); + + DEBUG_DVC("response size %d", length); + error = callback->channel->Write(callback->channel, length, stream_get_head(output), NULL); + if (error) + { + DEBUG_WARN("response error %d", error); + } + } + + stream_free(output); + + return error; +} + +static int tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + TSMF_STREAM* stream; + TSMF_PRESENTATION* presentation; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + + DEBUG_DVC(""); + + 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); + } + } + xfree(pChannelCallback); + + return 0; +} + +static int tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + uint8* Data, + int* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + TSMF_CHANNEL_CALLBACK* callback; + TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*) pListenerCallback; + + DEBUG_DVC(""); + + callback = xnew(TSMF_CHANNEL_CALLBACK); + callback->iface.OnDataReceived = tsmf_on_data_received; + callback->iface.OnClose = tsmf_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return 0; +} + +static int tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; + + DEBUG_DVC(""); + + tsmf->listener_callback = xnew(TSMF_LISTENER_CALLBACK); + tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection; + tsmf->listener_callback->plugin = pPlugin; + tsmf->listener_callback->channel_mgr = pChannelMgr; + return pChannelMgr->CreateListener(pChannelMgr, "TSMF", 0, + (IWTSListenerCallback*) tsmf->listener_callback, NULL); +} + +static int tsmf_plugin_terminated(IWTSPlugin* pPlugin) +{ + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; + + DEBUG_DVC(""); + + if (tsmf->listener_callback) + xfree(tsmf->listener_callback); + xfree(tsmf); + + return 0; +} + +static void tsmf_process_plugin_data(IWTSPlugin* pPlugin, RDP_PLUGIN_DATA* data) +{ + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; + + if (data->data[0] && strcmp((char*)data->data[0], "tsmf") == 0) + { + if (data->data[1] && strcmp((char*)data->data[1], "decoder") == 0) + { + tsmf->decoder_name = data->data[2]; + } + else if (data->data[1] && strcmp((char*)data->data[1], "audio") == 0) + { + tsmf->audio_name = data->data[2]; + tsmf->audio_device = data->data[3]; + } + } +} + +int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + TSMF_PLUGIN * tsmf; + int error = 0; + + tsmf = (TSMF_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "tsmf"); + if (tsmf == NULL) + { + tsmf = xnew(TSMF_PLUGIN); + + tsmf->iface.Initialize = tsmf_plugin_initialize; + tsmf->iface.Connected = NULL; + tsmf->iface.Disconnected = NULL; + tsmf->iface.Terminated = tsmf_plugin_terminated; + error = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", (IWTSPlugin*) tsmf); + + tsmf_media_init(); + } + if (error == 0) + { + tsmf_process_plugin_data((IWTSPlugin*) tsmf, + pEntryPoints->GetPluginData(pEntryPoints)); + } + return error; +} + diff --git a/channels/drdynvc/tsmf/tsmf_main.h b/channels/drdynvc/tsmf/tsmf_main.h new file mode 100644 index 000000000..deda1ceb6 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_main.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel + * + * 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 __TSMF_MAIN_H +#define __TSMF_MAIN_H + +void tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, + uint32 message_id, uint64 duration, uint32 data_size); +boolean tsmf_push_event(IWTSVirtualChannelCallback* pChannelCallback, + RDP_EVENT* event); + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_media.c b/channels/drdynvc/tsmf/tsmf_media.c new file mode 100644 index 000000000..7670bb6ec --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_media.c @@ -0,0 +1,795 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - Media Container + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drdynvc_types.h" +#include "tsmf_constants.h" +#include "tsmf_types.h" +#include "tsmf_decoder.h" +#include "tsmf_audio.h" +#include "tsmf_main.h" +#include "tsmf_codec.h" +#include "tsmf_media.h" + +#define AUDIO_TOLERANCE 10000000LL + +struct _TSMF_PRESENTATION +{ + uint8 presentation_id[GUID_SIZE]; + + const char* audio_name; + const char* audio_device; + int eos; + + uint32 last_x; + uint32 last_y; + uint32 last_width; + uint32 last_height; + uint16 last_num_rects; + RDP_RECT* last_rects; + + uint32 output_x; + uint32 output_y; + uint32 output_width; + uint32 output_height; + uint16 output_num_rects; + RDP_RECT* output_rects; + + IWTSVirtualChannelCallback* channel_callback; + + uint64 audio_start_time; + uint64 audio_end_time; + + /* The stream list could be accessed by differnt threads and need to be protected. */ + freerdp_mutex mutex; + + LIST* stream_list; +}; + +struct _TSMF_STREAM +{ + uint32 stream_id; + + TSMF_PRESENTATION* presentation; + + ITSMFDecoder* decoder; + + int major_type; + int eos; + uint32 width; + uint32 height; + + ITSMFAudioDevice* audio; + uint32 sample_rate; + uint32 channels; + uint32 bits_per_sample; + + /* The end_time of last played sample */ + uint64 last_end_time; + /* Next sample should not start before this system time. */ + uint64 next_start_time; + + freerdp_thread* thread; + + LIST* sample_list; + + /* The sample ack response queue will be accessed only by the stream thread. */ + LIST* sample_ack_list; +}; + +struct _TSMF_SAMPLE +{ + uint32 sample_id; + uint64 start_time; + uint64 end_time; + uint64 duration; + uint32 extensions; + uint32 data_size; + uint8* data; + uint32 decoded_size; + uint32 pixfmt; + + TSMF_STREAM* stream; + IWTSVirtualChannelCallback* channel_callback; + uint64 ack_time; +}; + +static LIST* presentation_list = NULL; + +static uint64 get_current_time(void) +{ + struct timeval tp; + + gettimeofday(&tp, 0); + return ((uint64)tp.tv_sec) * 10000000LL + ((uint64)tp.tv_usec) * 10LL; +} + +static TSMF_SAMPLE* tsmf_stream_pop_sample(TSMF_STREAM* stream, int sync) +{ + TSMF_STREAM* s; + LIST_ITEM* item; + TSMF_SAMPLE* sample; + boolean pending = False; + TSMF_PRESENTATION* presentation = stream->presentation; + + if (!stream->sample_list->head) + return NULL; + + if (sync) + { + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + /* Check if some other stream has earlier sample that needs to be played first */ + if (stream->last_end_time > AUDIO_TOLERANCE) + { + freerdp_mutex_lock(presentation->mutex); + for (item = presentation->stream_list->head; item; item = item->next) + { + s = (TSMF_STREAM*) item->data; + if (s != stream && !s->eos && s->last_end_time && + s->last_end_time < stream->last_end_time - AUDIO_TOLERANCE) + { + pending = True; + break; + } + } + freerdp_mutex_unlock(presentation->mutex); + } + } + else + { + if (stream->last_end_time > presentation->audio_end_time) + { + pending = True; + } + } + } + if (pending) + return NULL; + + freerdp_thread_lock(stream->thread); + sample = (TSMF_SAMPLE*) list_dequeue(stream->sample_list); + freerdp_thread_unlock(stream->thread); + + if (sample && sample->end_time > stream->last_end_time) + stream->last_end_time = sample->end_time; + + return sample; +} + +static void tsmf_sample_free(TSMF_SAMPLE* sample) +{ + if (sample->data) + xfree(sample->data); + xfree(sample); +} + +static void tsmf_sample_ack(TSMF_SAMPLE* sample) +{ + tsmf_playback_ack(sample->channel_callback, sample->sample_id, sample->duration, sample->data_size); +} + +static void tsmf_sample_queue_ack(TSMF_SAMPLE* sample) +{ + TSMF_STREAM* stream = sample->stream; + + list_enqueue(stream->sample_ack_list, sample); +} + +static void tsmf_stream_process_ack(TSMF_STREAM* stream) +{ + TSMF_SAMPLE* sample; + uint64 ack_time; + + ack_time = get_current_time(); + while (stream->sample_ack_list->head && !freerdp_thread_is_stopped(stream->thread)) + { + sample = (TSMF_SAMPLE*) list_peek(stream->sample_ack_list); + if (sample->ack_time > ack_time) + break; + + sample = list_dequeue(stream->sample_ack_list); + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + } +} + +TSMF_PRESENTATION* tsmf_presentation_new(const uint8* guid, IWTSVirtualChannelCallback* pChannelCallback) +{ + TSMF_PRESENTATION* presentation; + + presentation = tsmf_presentation_find_by_id(guid); + if (presentation) + { + DEBUG_WARN("duplicated presentation id!"); + return NULL; + } + + presentation = xnew(TSMF_PRESENTATION); + + memcpy(presentation->presentation_id, guid, GUID_SIZE); + presentation->channel_callback = pChannelCallback; + + presentation->mutex = freerdp_mutex_new(); + presentation->stream_list = list_new(); + + list_enqueue(presentation_list, presentation); + + return presentation; +} + +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const uint8* guid) +{ + LIST_ITEM* item; + TSMF_PRESENTATION* presentation; + + for (item = presentation_list->head; item; item = item->next) + { + presentation = (TSMF_PRESENTATION*) item->data; + if (memcmp(presentation->presentation_id, guid, GUID_SIZE) == 0) + return presentation; + } + return NULL; +} + +static void tsmf_presentation_restore_last_video_frame(TSMF_PRESENTATION* presentation) +{ + RDP_REDRAW_EVENT* revent; + + if (presentation->last_width && presentation->last_height) + { + revent = (RDP_REDRAW_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_TSMF, RDP_EVENT_TYPE_TSMF_REDRAW, + NULL, NULL); + revent->x = presentation->last_x; + revent->y = presentation->last_y; + revent->width = presentation->last_width; + revent->height = presentation->last_height; + if (!tsmf_push_event(presentation->channel_callback, (RDP_EVENT*) revent)) + { + freerdp_event_free((RDP_EVENT*) revent); + } + presentation->last_x = 0; + presentation->last_y = 0; + presentation->last_width = 0; + presentation->last_height = 0; + } +} + +static void tsmf_sample_playback_video(TSMF_SAMPLE* sample) +{ + uint64 t; + RDP_VIDEO_FRAME_EVENT* vevent; + TSMF_STREAM* stream = sample->stream; + TSMF_PRESENTATION* presentation = stream->presentation; + + DEBUG_DVC("MessageId %d EndTime %d data_size %d consumed.", + sample->sample_id, (int)sample->end_time, sample->data_size); + + if (sample->data) + { + t = get_current_time(); + if (stream->next_start_time > t && + (sample->end_time >= presentation->audio_start_time || + sample->end_time < stream->last_end_time)) + { + freerdp_usleep((stream->next_start_time - t) / 10); + } + stream->next_start_time = t + sample->duration - 50000; + + if (presentation->last_x != presentation->output_x || + presentation->last_y != presentation->output_y || + presentation->last_width != presentation->output_width || + presentation->last_height != presentation->output_height || + presentation->last_num_rects != presentation->output_num_rects || + (presentation->last_rects && presentation->output_rects && + memcmp(presentation->last_rects, presentation->output_rects, + presentation->last_num_rects * sizeof(RDP_RECT)) != 0)) + { + tsmf_presentation_restore_last_video_frame(presentation); + + presentation->last_x = presentation->output_x; + presentation->last_y = presentation->output_y; + presentation->last_width = presentation->output_width; + presentation->last_height = presentation->output_height; + + if (presentation->last_rects) + { + xfree(presentation->last_rects); + presentation->last_rects = NULL; + } + presentation->last_num_rects = presentation->output_num_rects; + if (presentation->last_num_rects > 0) + { + presentation->last_rects = xzalloc(presentation->last_num_rects * sizeof(RDP_RECT)); + memcpy(presentation->last_rects, presentation->output_rects, + presentation->last_num_rects * sizeof(RDP_RECT)); + } + } + + vevent = (RDP_VIDEO_FRAME_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_TSMF, RDP_EVENT_TYPE_TSMF_VIDEO_FRAME, + NULL, NULL); + vevent->frame_data = sample->data; + vevent->frame_size = sample->decoded_size; + vevent->frame_pixfmt = sample->pixfmt; + vevent->frame_width = sample->stream->width; + vevent->frame_height = sample->stream->height; + vevent->x = presentation->output_x; + vevent->y = presentation->output_y; + vevent->width = presentation->output_width; + vevent->height = presentation->output_height; + if (presentation->output_num_rects > 0) + { + vevent->num_visible_rects = presentation->output_num_rects; + vevent->visible_rects = (RDP_RECT*) xzalloc(presentation->output_num_rects * sizeof(RDP_RECT)); + memcpy(vevent->visible_rects, presentation->output_rects, + presentation->output_num_rects * sizeof(RDP_RECT)); + } + + /* The frame data ownership is passed to the event object, and is freed after the event is processed. */ + sample->data = NULL; + sample->decoded_size = 0; + + if (!tsmf_push_event(sample->channel_callback, (RDP_EVENT*) vevent)) + { + freerdp_event_free((RDP_EVENT*) vevent); + } + +#if 0 + /* Dump a .ppm image for every 30 frames. Assuming the frame is in YUV format, we + extract the Y values to create a grayscale image. */ + static int frame_id = 0; + char buf[100]; + FILE * fp; + if ((frame_id % 30) == 0) + { + snprintf(buf, sizeof(buf), "/tmp/FreeRDP_Frame_%d.ppm", frame_id); + fp = fopen(buf, "wb"); + fwrite("P5\n", 1, 3, fp); + snprintf(buf, sizeof(buf), "%d %d\n", sample->stream->width, sample->stream->height); + fwrite(buf, 1, strlen(buf), fp); + fwrite("255\n", 1, 4, fp); + fwrite(sample->data, 1, sample->stream->width * sample->stream->height, fp); + fflush(fp); + fclose(fp); + } + frame_id++; +#endif + } +} + +static void tsmf_sample_playback_audio(TSMF_SAMPLE* sample) +{ + uint64 latency = 0; + TSMF_STREAM* stream = sample->stream; + + DEBUG_DVC("MessageId %d EndTime %d consumed.", + sample->sample_id, (int)sample->end_time); + + if (sample->stream->audio && sample->data) + { + sample->stream->audio->Play(sample->stream->audio, + sample->data, sample->decoded_size); + sample->data = NULL; + sample->decoded_size = 0; + + if (stream->audio && stream->audio->GetLatency) + latency = stream->audio->GetLatency(stream->audio); + } + else + { + latency = 0; + } + + sample->ack_time = latency + get_current_time(); + stream->last_end_time = sample->end_time + latency; + stream->presentation->audio_start_time = sample->start_time + latency; + stream->presentation->audio_end_time = sample->end_time + latency; +} + +static void tsmf_sample_playback(TSMF_SAMPLE* sample) +{ + boolean ret = False; + uint32 width; + uint32 height; + uint32 pixfmt = 0; + TSMF_STREAM* stream = sample->stream; + + if (stream->decoder) + ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size, sample->extensions); + if (!ret) + { + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + return; + } + + xfree(sample->data); + sample->data = NULL; + + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + if (stream->decoder->GetDecodedFormat) + { + pixfmt = stream->decoder->GetDecodedFormat(stream->decoder); + if (pixfmt == ((uint32) -1)) + { + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + return; + } + sample->pixfmt = pixfmt; + } + + if (stream->decoder->GetDecodedDimension) + ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height); + if (ret && (width != stream->width || height != stream->height)) + { + DEBUG_DVC("video dimension changed to %d x %d", width, height); + stream->width = width; + stream->height = height; + } + } + + if (stream->decoder->GetDecodedData) + { + sample->data = stream->decoder->GetDecodedData(stream->decoder, &sample->decoded_size); + } + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + tsmf_sample_playback_video(sample); + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + break; + case TSMF_MAJOR_TYPE_AUDIO: + tsmf_sample_playback_audio(sample); + tsmf_sample_queue_ack(sample); + break; + } +} + +static void* tsmf_stream_playback_func(void* arg) +{ + TSMF_SAMPLE* sample; + TSMF_STREAM* stream = (TSMF_STREAM*) arg; + TSMF_PRESENTATION* presentation = stream->presentation; + + DEBUG_DVC("in %d", stream->stream_id); + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO && + stream->sample_rate && stream->channels && stream->bits_per_sample) + { + stream->audio = tsmf_load_audio_device( + presentation->audio_name && presentation->audio_name[0] ? presentation->audio_name : NULL, + presentation->audio_device && presentation->audio_device[0] ? presentation->audio_device : NULL); + if (stream->audio) + { + stream->audio->SetFormat(stream->audio, + stream->sample_rate, stream->channels, stream->bits_per_sample); + } + } + while (!freerdp_thread_is_stopped(stream->thread)) + { + tsmf_stream_process_ack(stream); + sample = tsmf_stream_pop_sample(stream, 1); + if (sample) + tsmf_sample_playback(sample); + else + freerdp_usleep(5000); + } + if (stream->eos || presentation->eos) + { + while ((sample = tsmf_stream_pop_sample(stream, 1)) != NULL) + tsmf_sample_playback(sample); + } + if (stream->audio) + { + stream->audio->Free(stream->audio); + stream->audio = NULL; + } + + freerdp_thread_quit(stream->thread); + + DEBUG_DVC("out %d", stream->stream_id); + + return NULL; +} + +static void tsmf_stream_start(TSMF_STREAM* stream) +{ + if (!freerdp_thread_is_running(stream->thread)) + { + freerdp_thread_start(stream->thread, tsmf_stream_playback_func, stream); + } +} + +static void tsmf_stream_stop(TSMF_STREAM* stream) +{ + if (freerdp_thread_is_running(stream->thread)) + { + freerdp_thread_stop(stream->thread); + } +} + +void tsmf_presentation_start(TSMF_PRESENTATION* presentation) +{ + LIST_ITEM* item; + TSMF_STREAM* stream; + + for (item = presentation->stream_list->head; item; item = item->next) + { + stream = (TSMF_STREAM*) item->data; + tsmf_stream_start(stream); + } +} + +void tsmf_presentation_stop(TSMF_PRESENTATION* presentation) +{ + LIST_ITEM* item; + TSMF_STREAM* stream; + + tsmf_presentation_flush(presentation); + + for (item = presentation->stream_list->head; item; item = item->next) + { + stream = (TSMF_STREAM*) item->data; + tsmf_stream_stop(stream); + } + + tsmf_presentation_restore_last_video_frame(presentation); + if (presentation->last_rects) + { + xfree(presentation->last_rects); + presentation->last_rects = NULL; + } + presentation->last_num_rects = 0; + if (presentation->output_rects) + { + xfree(presentation->output_rects); + presentation->output_rects = NULL; + } + presentation->output_num_rects = 0; +} + +void tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, + uint32 x, uint32 y, uint32 width, uint32 height, + int num_rects, RDP_RECT* rects) +{ + presentation->output_x = x; + presentation->output_y = y; + presentation->output_width = width; + presentation->output_height = height; + if (presentation->output_rects) + xfree(presentation->output_rects); + presentation->output_rects = rects; + presentation->output_num_rects = num_rects; +} + +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name, const char* device) +{ + presentation->audio_name = name; + presentation->audio_device = device; +} + +static void tsmf_stream_flush(TSMF_STREAM* stream) +{ + TSMF_SAMPLE* sample; + + while ((sample = tsmf_stream_pop_sample(stream, 0)) != NULL) + tsmf_sample_free(sample); + + while ((sample = list_dequeue(stream->sample_ack_list)) != NULL) + tsmf_sample_free(sample); + + if (stream->audio) + stream->audio->Flush(stream->audio); + + stream->eos = 0; + stream->last_end_time = 0; + stream->next_start_time = 0; + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + stream->presentation->audio_start_time = 0; + stream->presentation->audio_end_time = 0; + } +} + +void tsmf_presentation_flush(TSMF_PRESENTATION* presentation) +{ + LIST_ITEM* item; + TSMF_STREAM * stream; + + for (item = presentation->stream_list->head; item; item = item->next) + { + stream = (TSMF_STREAM*) item->data; + tsmf_stream_flush(stream); + } + + presentation->eos = 0; + presentation->audio_start_time = 0; + presentation->audio_end_time = 0; +} + +void tsmf_presentation_free(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream; + + tsmf_presentation_stop(presentation); + list_remove(presentation_list, presentation); + + while (presentation->stream_list->head) + { + stream = (TSMF_STREAM*) list_peek(presentation->stream_list); + tsmf_stream_free(stream); + } + list_free(presentation->stream_list); + + freerdp_mutex_free(presentation->mutex); + + xfree(presentation); +} + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, uint32 stream_id) +{ + TSMF_STREAM* stream; + + stream = tsmf_stream_find_by_id(presentation, stream_id); + if (stream) + { + DEBUG_WARN("duplicated stream id %d!", stream_id); + return NULL; + } + + stream = xnew(TSMF_STREAM); + + stream->stream_id = stream_id; + stream->presentation = presentation; + stream->thread = freerdp_thread_new(); + stream->sample_list = list_new(); + stream->sample_ack_list = list_new(); + + freerdp_mutex_lock(presentation->mutex); + list_enqueue(presentation->stream_list, stream); + freerdp_mutex_unlock(presentation->mutex); + + return stream; +} + +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, uint32 stream_id) +{ + LIST_ITEM* item; + TSMF_STREAM* stream; + + for (item = presentation->stream_list->head; item; item = item->next) + { + stream = (TSMF_STREAM*) item->data; + if (stream->stream_id == stream_id) + return stream; + } + return NULL; +} + +void tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, STREAM* s) +{ + TS_AM_MEDIA_TYPE mediatype; + + if (stream->decoder) + { + DEBUG_WARN("duplicated call"); + return; + } + + tsmf_codec_parse_media_type(&mediatype, s); + + if (mediatype.MajorType == TSMF_MAJOR_TYPE_VIDEO) + { + DEBUG_DVC("video width %d height %d bit_rate %d frame_rate %f codec_data %d", + mediatype.Width, mediatype.Height, mediatype.BitRate, + (double)mediatype.SamplesPerSecond.Numerator / (double)mediatype.SamplesPerSecond.Denominator, + mediatype.ExtraDataSize); + } + else if (mediatype.MajorType == TSMF_MAJOR_TYPE_AUDIO) + { + DEBUG_DVC("audio channel %d sample_rate %d bits_per_sample %d codec_data %d", + mediatype.Channels, mediatype.SamplesPerSecond.Numerator, mediatype.BitsPerSample, + mediatype.ExtraDataSize); + stream->sample_rate = mediatype.SamplesPerSecond.Numerator; + stream->channels = mediatype.Channels; + stream->bits_per_sample = mediatype.BitsPerSample; + if (stream->bits_per_sample == 0) + stream->bits_per_sample = 16; + } + + stream->major_type = mediatype.MajorType; + stream->width = mediatype.Width; + stream->height = mediatype.Height; + stream->decoder = tsmf_load_decoder(name, &mediatype); +} + +void tsmf_stream_end(TSMF_STREAM* stream) +{ + stream->eos = 1; + stream->presentation->eos = 1; +} + +void tsmf_stream_free(TSMF_STREAM* stream) +{ + TSMF_PRESENTATION* presentation = stream->presentation; + + tsmf_stream_stop(stream); + tsmf_stream_flush(stream); + + freerdp_mutex_lock(presentation->mutex); + list_remove(presentation->stream_list, stream); + freerdp_mutex_unlock(presentation->mutex); + + list_free(stream->sample_list); + list_free(stream->sample_ack_list); + + if (stream->decoder) + stream->decoder->Free(stream->decoder); + + freerdp_thread_free(stream->thread); + + xfree(stream); +} + +void 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, uint8* data) +{ + TSMF_SAMPLE* sample; + + sample = xnew(TSMF_SAMPLE); + + sample->sample_id = sample_id; + sample->start_time = start_time; + sample->end_time = end_time; + sample->duration = duration; + sample->extensions = extensions; + sample->stream = stream; + sample->channel_callback = pChannelCallback; + sample->data_size = data_size; + sample->data = xzalloc(data_size + TSMF_BUFFER_PADDING_SIZE); + memcpy(sample->data, data, data_size); + + freerdp_thread_lock(stream->thread); + list_enqueue(stream->sample_list, sample); + freerdp_thread_unlock(stream->thread); +} + +void tsmf_media_init(void) +{ + if (presentation_list == NULL) + presentation_list = list_new(); +} + diff --git a/channels/drdynvc/tsmf/tsmf_media.h b/channels/drdynvc/tsmf/tsmf_media.h new file mode 100644 index 000000000..eac3e0035 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_media.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - Media Container + * + * 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. + */ + +/** + * The media container maintains a global list of presentations, and a list of + * streams in each presentation. + */ + +#ifndef __TSMF_MEDIA_H +#define __TSMF_MEDIA_H + +typedef struct _TSMF_PRESENTATION TSMF_PRESENTATION; + +typedef struct _TSMF_STREAM TSMF_STREAM; + +typedef struct _TSMF_SAMPLE TSMF_SAMPLE; + +TSMF_PRESENTATION* tsmf_presentation_new(const uint8* guid, IWTSVirtualChannelCallback* pChannelCallback); +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const uint8* guid); +void tsmf_presentation_start(TSMF_PRESENTATION* presentation); +void tsmf_presentation_stop(TSMF_PRESENTATION* presentation); +void tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, + uint32 x, uint32 y, uint32 width, uint32 height, + int num_rects, RDP_RECT* rects); +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, + const char* name, const char* device); +void tsmf_presentation_flush(TSMF_PRESENTATION* presentation); +void tsmf_presentation_free(TSMF_PRESENTATION* presentation); + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, uint32 stream_id); +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, uint32 stream_id); +void tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, STREAM* s); +void tsmf_stream_end(TSMF_STREAM* stream); +void tsmf_stream_free(TSMF_STREAM* stream); + +void 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, uint8* data); + +void tsmf_media_init(void); + +#endif + diff --git a/channels/drdynvc/tsmf/tsmf_types.h b/channels/drdynvc/tsmf/tsmf_types.h new file mode 100644 index 000000000..20745cb92 --- /dev/null +++ b/channels/drdynvc/tsmf/tsmf_types.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 __TSMF_TYPES_H +#define __TSMF_TYPES_H + +#include + +typedef struct _TS_AM_MEDIA_TYPE +{ + 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 uint8* ExtraData; + uint32 ExtraDataSize; +} TS_AM_MEDIA_TYPE; + +#endif + From ebaf94d6d6a1678e840f4c1a1a0853bdce8e8dc7 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Tue, 20 Sep 2011 14:27:10 +0800 Subject: [PATCH 6/9] tsmf: add ffmpeg, alsa and pulse sub-plugins. --- CMakeLists.txt | 1 + channels/drdynvc/tsmf/CMakeLists.txt | 12 + channels/drdynvc/tsmf/alsa/CMakeLists.txt | 34 ++ channels/drdynvc/tsmf/alsa/tsmf_alsa.c | 264 ++++++++++ channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt | 34 ++ channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c | 514 ++++++++++++++++++++ channels/drdynvc/tsmf/pulse/CMakeLists.txt | 34 ++ channels/drdynvc/tsmf/pulse/tsmf_pulse.c | 402 +++++++++++++++ cmake/FindFFmpeg.cmake | 1 + include/freerdp/plugins/tsmf.h | 5 + 10 files changed, 1301 insertions(+) create mode 100644 channels/drdynvc/tsmf/alsa/CMakeLists.txt create mode 100644 channels/drdynvc/tsmf/alsa/tsmf_alsa.c create mode 100644 channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt create mode 100644 channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c create mode 100644 channels/drdynvc/tsmf/pulse/CMakeLists.txt create mode 100644 channels/drdynvc/tsmf/pulse/tsmf_pulse.c create mode 100644 cmake/FindFFmpeg.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 215139a26..4a0b298e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ if(NOT WIN32) find_suggested_package(ALSA) find_optional_package(PulseAudio) find_suggested_package(Cups) + find_suggested_package(FFmpeg) endif() # Endian diff --git a/channels/drdynvc/tsmf/CMakeLists.txt b/channels/drdynvc/tsmf/CMakeLists.txt index 98e25058d..7df0e3a35 100644 --- a/channels/drdynvc/tsmf/CMakeLists.txt +++ b/channels/drdynvc/tsmf/CMakeLists.txt @@ -43,3 +43,15 @@ target_link_libraries(tsmf freerdp-utils) install(TARGETS tsmf DESTINATION ${FREERDP_PLUGIN_PATH}) +if(FFMPEG_FOUND) + add_subdirectory(ffmpeg) +endif() + +if(ALSA_FOUND) + add_subdirectory(alsa) +endif() + +if(PULSE_FOUND) + add_subdirectory(pulse) +endif() + diff --git a/channels/drdynvc/tsmf/alsa/CMakeLists.txt b/channels/drdynvc/tsmf/alsa/CMakeLists.txt new file mode 100644 index 000000000..c769a1857 --- /dev/null +++ b/channels/drdynvc/tsmf/alsa/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(TSMF_ALSA_SRCS + tsmf_alsa.c +) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_library(tsmf_alsa ${TSMF_ALSA_SRCS}) +set_target_properties(tsmf_alsa PROPERTIES PREFIX "") + +target_link_libraries(tsmf_alsa freerdp-utils) +target_link_libraries(tsmf_alsa ${ALSA_LIBRARIES}) + +install(TARGETS tsmf_alsa DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/alsa/tsmf_alsa.c b/channels/drdynvc/tsmf/alsa/tsmf_alsa.c new file mode 100644 index 000000000..39bc8b7a4 --- /dev/null +++ b/channels/drdynvc/tsmf/alsa/tsmf_alsa.c @@ -0,0 +1,264 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tsmf_audio.h" + +typedef struct _TSMFALSAAudioDevice +{ + 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 boolean tsmf_alsa_open_device(TSMFALSAAudioDevice* alsa) +{ + int error; + + error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0); + if (error < 0) + { + DEBUG_WARN("failed to open device %s", alsa->device); + return False; + } + + DEBUG_DVC("open device %s", alsa->device); + return True; +} + +static boolean tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + if (!device) + { + if (!alsa->device[0]) + strcpy(alsa->device, "default"); + } + else + { + strcpy(alsa->device, device); + } + + return tsmf_alsa_open_device(alsa); +} + +static boolean tsmf_alsa_set_format(ITSMFAudioDevice* audio, + uint32 sample_rate, uint32 channels, uint32 bits_per_sample) +{ + int error; + snd_pcm_uframes_t frames; + snd_pcm_hw_params_t* hw_params; + snd_pcm_sw_params_t* sw_params; + 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) + { + DEBUG_WARN("snd_pcm_hw_params_malloc failed"); + return False; + } + snd_pcm_hw_params_any(alsa->out_handle, hw_params); + snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, + SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, + &alsa->actual_rate, NULL); + snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, + &alsa->actual_channels); + frames = sample_rate; + snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, + &frames); + snd_pcm_hw_params(alsa->out_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + + error = snd_pcm_sw_params_malloc(&sw_params); + if (error < 0) + { + DEBUG_WARN("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_DVC("sample_rate %d channels %d bits_per_sample %d", + sample_rate, channels, bits_per_sample); + DEBUG_DVC("hardware buffer %d frames", (int)frames); + if ((alsa->actual_rate != alsa->source_rate) || + (alsa->actual_channels != alsa->source_channels)) + { + DEBUG_DVC("actual rate %d / channel %d is different " + "from source rate %d / channel %d, resampling required.", + alsa->actual_rate, alsa->actual_channels, + alsa->source_rate, alsa->source_channels); + } + return True; +} + +static boolean tsmf_alsa_play(ITSMFAudioDevice* audio, uint8* data, uint32 data_size) +{ + int len; + int error; + int frames; + uint8* end; + uint8* src; + uint8* pindex; + int rbytes_per_frame; + int sbytes_per_frame; + uint8* resampled_data; + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + DEBUG_DVC("data_size %d", data_size); + + if (alsa->out_handle) + { + sbytes_per_frame = alsa->source_channels * alsa->bytes_per_sample; + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_sample; + + if ((alsa->source_rate == alsa->actual_rate) && + (alsa->source_channels == alsa->actual_channels)) + { + resampled_data = NULL; + src = data; + data_size = data_size; + } + else + { + resampled_data = dsp_resample(data, alsa->bytes_per_sample, + alsa->source_channels, alsa->source_rate, data_size / sbytes_per_frame, + alsa->actual_channels, alsa->actual_rate, &frames); + DEBUG_DVC("resampled %d frames at %d to %d frames at %d", + data_size / sbytes_per_frame, alsa->source_rate, frames, alsa->actual_rate); + data_size = frames * rbytes_per_frame; + src = resampled_data; + } + + pindex = src; + end = pindex + data_size; + while (pindex < end) + { + len = end - pindex; + frames = len / rbytes_per_frame; + error = snd_pcm_writei(alsa->out_handle, pindex, frames); + if (error == -EPIPE) + { + snd_pcm_recover(alsa->out_handle, error, 0); + error = 0; + } + else if (error < 0) + { + DEBUG_DVC("error len %d", error); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + tsmf_alsa_open_device(alsa); + break; + } + DEBUG_DVC("%d frames played.", error); + if (error == 0) + break; + pindex += error * rbytes_per_frame; + } + + if (resampled_data) + xfree(resampled_data); + } + xfree(data); + + 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 void tsmf_alsa_flush(ITSMFAudioDevice* audio) +{ +} + +static void tsmf_alsa_free(ITSMFAudioDevice* audio) +{ + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + DEBUG_DVC(""); + + if (alsa->out_handle) + { + snd_pcm_drain(alsa->out_handle); + snd_pcm_close(alsa->out_handle); + } + xfree(alsa); +} + +ITSMFAudioDevice* TSMFAudioDeviceEntry(void) +{ + TSMFALSAAudioDevice* alsa; + + alsa = xnew(TSMFALSAAudioDevice); + + alsa->iface.Open = tsmf_alsa_open; + alsa->iface.SetFormat = tsmf_alsa_set_format; + alsa->iface.Play = tsmf_alsa_play; + alsa->iface.GetLatency = tsmf_alsa_get_latency; + alsa->iface.Flush = tsmf_alsa_flush; + alsa->iface.Free = tsmf_alsa_free; + + return (ITSMFAudioDevice*) alsa; +} + diff --git a/channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt b/channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt new file mode 100644 index 000000000..ea0b1010b --- /dev/null +++ b/channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(TSMF_FFMPEG_SRCS + tsmf_ffmpeg.c +) + +include_directories(..) +include_directories(${FFMPEG_INCLUDE_DIRS}) + +add_library(tsmf_ffmpeg ${TSMF_FFMPEG_SRCS}) +set_target_properties(tsmf_ffmpeg PROPERTIES PREFIX "") + +target_link_libraries(tsmf_ffmpeg freerdp-utils) +target_link_libraries(tsmf_ffmpeg ${FFMPEG_LIBRARIES}) + +install(TARGETS tsmf_ffmpeg DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c b/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c new file mode 100644 index 000000000..a41187fb0 --- /dev/null +++ b/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c @@ -0,0 +1,514 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 +#include +#include +#include +#include +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +/* Compatibility with older FFmpeg */ +#if LIBAVUTIL_VERSION_MAJOR < 50 +#define AVMEDIA_TYPE_VIDEO 0 +#define AVMEDIA_TYPE_AUDIO 1 +#endif + +typedef struct _TSMFFFmpegDecoder +{ + ITSMFDecoder iface; + + int media_type; + enum CodecID codec_id; + AVCodecContext* codec_context; + AVCodec* codec; + AVFrame* frame; + int prepared; + + uint8* decoded_data; + uint32 decoded_size; + uint32 decoded_size_max; +} TSMFFFmpegDecoder; + +static boolean tsmf_ffmpeg_init_context(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + mdecoder->codec_context = avcodec_alloc_context(); + if (!mdecoder->codec_context) + { + DEBUG_WARN("avcodec_alloc_context failed."); + return False; + } + + return True; +} + +static boolean tsmf_ffmpeg_init_video_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + mdecoder->codec_context->width = media_type->Width; + mdecoder->codec_context->height = media_type->Height; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->time_base.den = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->time_base.num = media_type->SamplesPerSecond.Denominator; + + mdecoder->frame = avcodec_alloc_frame(); + + return True; +} + +static boolean tsmf_ffmpeg_init_audio_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + mdecoder->codec_context->sample_rate = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->channels = media_type->Channels; + mdecoder->codec_context->block_align = media_type->BlockAlign; + +#ifdef AV_CPU_FLAG_SSE2 + mdecoder->codec_context->dsp_mask = AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMX2; +#else + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMX2; +#endif + + return True; +} + +static boolean tsmf_ffmpeg_init_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + uint32 size; + const uint8* s; + uint8* p; + + mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id); + if (!mdecoder->codec) + { + DEBUG_WARN("avcodec_find_decoder failed."); + return False; + } + + mdecoder->codec_context->codec_id = mdecoder->codec_id; + mdecoder->codec_context->codec_type = mdecoder->media_type; + + if (mdecoder->media_type == AVMEDIA_TYPE_VIDEO) + { + if (!tsmf_ffmpeg_init_video_stream(decoder, media_type)) + return False; + } + else if (mdecoder->media_type == AVMEDIA_TYPE_AUDIO) + { + if (!tsmf_ffmpeg_init_audio_stream(decoder, media_type)) + return False; + } + + if (media_type->ExtraData) + { + if (media_type->SubType == TSMF_SUB_TYPE_AVC1 && + media_type->FormatType == TSMF_FORMAT_TYPE_MPEG2VIDEOINFO) + { + /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska. + See http://haali.su/mkv/codecs.pdf */ + mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8; + mdecoder->codec_context->extradata = xzalloc(mdecoder->codec_context->extradata_size); + p = mdecoder->codec_context->extradata; + *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))); + memcpy(p, s, size + 2); + s += size + 2; + p += size + 2; + *p++ = 1; /* #pps */ + size = ((uint32)(*s)) * 256 + ((uint32)(*(s + 1))); + memcpy(p, s, size + 2); + } + else + { + /* Add a padding to avoid invalid memory read in some codec */ + mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8; + mdecoder->codec_context->extradata = xzalloc(mdecoder->codec_context->extradata_size); + memcpy(mdecoder->codec_context->extradata, media_type->ExtraData, media_type->ExtraDataSize); + memset(mdecoder->codec_context->extradata + media_type->ExtraDataSize, 0, 8); + } + } + + if (mdecoder->codec->capabilities & CODEC_CAP_TRUNCATED) + mdecoder->codec_context->flags |= CODEC_FLAG_TRUNCATED; + + return True; +} + +static boolean tsmf_ffmpeg_prepare(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (avcodec_open(mdecoder->codec_context, mdecoder->codec) < 0) + { + DEBUG_WARN("avcodec_open failed."); + return False; + } + + mdecoder->prepared = 1; + + return True; +} + +static boolean tsmf_ffmpeg_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + 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 = CODEC_ID_VC1; + break; + case TSMF_SUB_TYPE_WMA2: + mdecoder->codec_id = CODEC_ID_WMAV2; + break; + case TSMF_SUB_TYPE_WMA9: + mdecoder->codec_id = CODEC_ID_WMAPRO; + break; + case TSMF_SUB_TYPE_MP3: + mdecoder->codec_id = CODEC_ID_MP3; + break; + case TSMF_SUB_TYPE_MP2A: + mdecoder->codec_id = CODEC_ID_MP2; + break; + case TSMF_SUB_TYPE_MP2V: + mdecoder->codec_id = CODEC_ID_MPEG2VIDEO; + break; + case TSMF_SUB_TYPE_WMV3: + mdecoder->codec_id = CODEC_ID_WMV3; + break; + case TSMF_SUB_TYPE_AAC: + mdecoder->codec_id = 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) + { + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + break; + case TSMF_SUB_TYPE_H264: + case TSMF_SUB_TYPE_AVC1: + mdecoder->codec_id = CODEC_ID_H264; + break; + case TSMF_SUB_TYPE_AC3: + mdecoder->codec_id = 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 boolean tsmf_ffmpeg_decode_video(ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + int decoded; + int len; + AVFrame* frame; + boolean ret = True; + +#if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_video(mdecoder->codec_context, mdecoder->frame, &decoded, data, data_size); +#else + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (uint8*) data; + pkt.size = data_size; + if (extensions & TSMM_SAMPLE_EXT_CLEANPOINT) + pkt.flags |= AV_PKT_FLAG_KEY; + len = avcodec_decode_video2(mdecoder->codec_context, mdecoder->frame, &decoded, &pkt); + } +#endif + + if (len < 0) + { + DEBUG_WARN("data_size %d, avcodec_decode_video failed (%d)", data_size, len); + ret = False; + } + else if (!decoded) + { + DEBUG_WARN("data_size %d, no frame is decoded.", data_size); + ret = False; + } + else + { + DEBUG_DVC("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 = avpicture_get_size(mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + mdecoder->decoded_data = xzalloc(mdecoder->decoded_size); + frame = avcodec_alloc_frame(); + avpicture_fill((AVPicture *) frame, mdecoder->decoded_data, + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + + av_picture_copy((AVPicture *) frame, (AVPicture *) mdecoder->frame, + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + + av_free(frame); + } + + return ret; +} + +static boolean tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + int len; + int frame_size; + uint32 src_size; + const uint8* src; + uint8* dst; + int dst_offset; + +#if 0 + LLOGLN(0, ("tsmf_ffmpeg_decode_audio: data_size %d", data_size)); + int i; + for (i = 0; i < data_size; i++) + { + LLOG(0, ("%02X ", data[i])); + if (i % 16 == 15) + LLOG(0, ("\n")); + } + LLOG(0, ("\n")); +#endif + + if (mdecoder->decoded_size_max == 0) + mdecoder->decoded_size_max = AVCODEC_MAX_AUDIO_FRAME_SIZE + 16; + mdecoder->decoded_data = xzalloc(mdecoder->decoded_size_max); + /* align the memory for SSE2 needs */ + dst = (uint8*) (((uintptr_t)mdecoder->decoded_data + 15) & ~ 0x0F); + dst_offset = dst - mdecoder->decoded_data; + src = data; + src_size = data_size; + + while (src_size > 0) + { + /* Ensure enough space for decoding */ + if (mdecoder->decoded_size_max - mdecoder->decoded_size < AVCODEC_MAX_AUDIO_FRAME_SIZE) + { + mdecoder->decoded_size_max *= 2; + mdecoder->decoded_data = xrealloc(mdecoder->decoded_data, mdecoder->decoded_size_max); + dst = (uint8*) (((uintptr_t)mdecoder->decoded_data + 15) & ~ 0x0F); + if (dst - mdecoder->decoded_data != dst_offset) + { + /* re-align the memory if the alignment has changed after realloc */ + memmove(dst, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); + dst_offset = dst - mdecoder->decoded_data; + } + dst += mdecoder->decoded_size; + } + frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size; +#if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_audio2(mdecoder->codec_context, + (int16_t*) dst, &frame_size, + src, src_size); +#else + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (uint8*) src; + pkt.size = src_size; + len = avcodec_decode_audio3(mdecoder->codec_context, + (int16_t*) dst, &frame_size, &pkt); + } +#endif + if (len <= 0 || frame_size <= 0) + { + DEBUG_WARN("error decoding"); + break; + } + src += len; + src_size -= len; + mdecoder->decoded_size += frame_size; + dst += frame_size; + } + + if (mdecoder->decoded_size == 0) + { + xfree(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + else if (dst_offset) + { + /* move the aligned decoded data to original place */ + memmove(mdecoder->decoded_data, dst, mdecoder->decoded_size); + } + + DEBUG_DVC("data_size %d decoded_size %d", + data_size, mdecoder->decoded_size); + + return True; +} + +static boolean tsmf_ffmpeg_decode(ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->decoded_data) + { + xfree(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + mdecoder->decoded_size = 0; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + return tsmf_ffmpeg_decode_video(decoder, data, data_size, extensions); + case AVMEDIA_TYPE_AUDIO: + return tsmf_ffmpeg_decode_audio(decoder, data, data_size, extensions); + default: + DEBUG_WARN("unknown media type."); + return False; + } +} + +static uint8* tsmf_ffmpeg_get_decoded_data(ITSMFDecoder* decoder, uint32* size) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + uint8* buf; + + *size = mdecoder->decoded_size; + buf = mdecoder->decoded_data; + mdecoder->decoded_data = NULL; + mdecoder->decoded_size = 0; + return buf; +} + +static uint32 tsmf_ffmpeg_get_decoded_format(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + switch (mdecoder->codec_context->pix_fmt) + { + case PIX_FMT_YUV420P: + return RDP_PIXFMT_I420; + + default: + DEBUG_WARN("unsupported pixel format %u", + mdecoder->codec_context->pix_fmt); + return (uint32) -1; + } +} + +static boolean 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); + if (mdecoder->decoded_data) + xfree(mdecoder->decoded_data); + if (mdecoder->codec_context) + { + if (mdecoder->prepared) + avcodec_close(mdecoder->codec_context); + if (mdecoder->codec_context->extradata) + xfree(mdecoder->codec_context->extradata); + av_free(mdecoder->codec_context); + } + xfree(decoder); +} + +static boolean initialized = False; + +ITSMFDecoder* +TSMFDecoderEntry(void) +{ + TSMFFFmpegDecoder * decoder; + + if (!initialized) + { + avcodec_init(); + avcodec_register_all(); + initialized = True; + } + + decoder = xnew(TSMFFFmpegDecoder); + + decoder->iface.SetFormat = tsmf_ffmpeg_set_format; + decoder->iface.Decode = tsmf_ffmpeg_decode; + decoder->iface.GetDecodedData = tsmf_ffmpeg_get_decoded_data; + decoder->iface.GetDecodedFormat = tsmf_ffmpeg_get_decoded_format; + decoder->iface.GetDecodedDimension = tsmf_ffmpeg_get_decoded_dimension; + decoder->iface.Free = tsmf_ffmpeg_free; + + return (ITSMFDecoder*) decoder; +} + diff --git a/channels/drdynvc/tsmf/pulse/CMakeLists.txt b/channels/drdynvc/tsmf/pulse/CMakeLists.txt new file mode 100644 index 000000000..373d4d32b --- /dev/null +++ b/channels/drdynvc/tsmf/pulse/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(TSMF_PULSE_SRCS + tsmf_pulse.c +) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIRS}) + +add_library(tsmf_pulse ${TSMF_PULSE_SRCS}) +set_target_properties(tsmf_pulse PROPERTIES PREFIX "") + +target_link_libraries(tsmf_pulse freerdp-utils) +target_link_libraries(tsmf_pulse ${PULSE_LIBRARIES}) + +install(TARGETS tsmf_pulse DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/pulse/tsmf_pulse.c b/channels/drdynvc/tsmf/pulse/tsmf_pulse.c new file mode 100644 index 000000000..4d4ecee25 --- /dev/null +++ b/channels/drdynvc/tsmf/pulse/tsmf_pulse.c @@ -0,0 +1,402 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * 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 +#include +#include +#include +#include +#include + +#include "tsmf_audio.h" + +typedef struct _TSMFPulseAudioDevice +{ + 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; + + state = pa_context_get_state(context); + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_DVC("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_DVC("state %d", (int)state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_DVC("state %d", (int)state); + break; + } +} + +static boolean tsmf_pulse_connect(TSMFPulseAudioDevice* pulse) +{ + pa_context_state_t state; + + if (!pulse->context) + return False; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + DEBUG_WARN("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); + DEBUG_WARN("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_DVC("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_DVC("connected"); + return True; + } + else + { + pa_context_disconnect(pulse->context); + return False; + } +} + +static boolean tsmf_pulse_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + if (device) + { + strcpy(pulse->device, device); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + if (!pulse->mainloop) + { + DEBUG_WARN("pa_threaded_mainloop_new failed"); + return False; + } + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + if (!pulse->context) + { + DEBUG_WARN("pa_context_new failed"); + return False; + } + pa_context_set_state_callback(pulse->context, tsmf_pulse_context_state_callback, pulse); + if (tsmf_pulse_connect(pulse)) + { + DEBUG_WARN("tsmf_pulse_connect failed"); + return False; + } + + DEBUG_DVC("open device %s", pulse->device); + return True; +} + +static void tsmf_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void tsmf_pulse_wait_for_operation(TSMFPulseAudioDevice* pulse, pa_operation* operation) +{ + if (operation == NULL) + return; + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_operation_unref(operation); +} + +static void tsmf_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + pa_stream_state_t state; + + state = pa_stream_get_state(stream); + switch (state) + { + case PA_STREAM_READY: + DEBUG_DVC("PA_STREAM_READY"); + pa_threaded_mainloop_signal (pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_DVC("state %d", (int)state); + pa_threaded_mainloop_signal (pulse->mainloop, 0); + break; + + default: + DEBUG_DVC("state %d", (int)state); + break; + } +} + +static void tsmf_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + + DEBUG_DVC("%d", (int) length); + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static boolean tsmf_pulse_close_stream(TSMFPulseAudioDevice* pulse) +{ + if (!pulse->context || !pulse->stream) + return False; + + DEBUG_DVC(""); + + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_set_write_callback(pulse->stream, NULL, NULL); + tsmf_pulse_wait_for_operation(pulse, + pa_stream_drain(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + + return True; +} + +static boolean tsmf_pulse_open_stream(TSMFPulseAudioDevice* pulse) +{ + pa_stream_state_t state; + pa_buffer_attr buffer_attr = { 0 }; + + if (!pulse->context) + return False; + + DEBUG_DVC(""); + + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp", + &pulse->sample_spec, NULL); + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + pa_stream_set_state_callback(pulse->stream, + tsmf_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, + tsmf_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = pa_usec_to_bytes(500000, &pulse->sample_spec); + buffer_attr.tlength = pa_usec_to_bytes(250000, &pulse->sample_spec); + buffer_attr.prebuf = (uint32_t) -1; + buffer_attr.minreq = (uint32_t) -1; + buffer_attr.fragsize = (uint32_t) -1; + if (pa_stream_connect_playback(pulse->stream, + pulse->device[0] ? pulse->device : NULL, &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, + NULL, NULL) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("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)) + { + DEBUG_WARN("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_DVC("connected"); + return True; + } + else + { + tsmf_pulse_close_stream(pulse); + return False; + } +} + +static boolean tsmf_pulse_set_format(ITSMFAudioDevice* audio, + uint32 sample_rate, uint32 channels, uint32 bits_per_sample) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + DEBUG_DVC("sample_rate %d channels %d bits_per_sample %d", + sample_rate, channels, bits_per_sample); + + pulse->sample_spec.rate = sample_rate; + pulse->sample_spec.channels = channels; + pulse->sample_spec.format = PA_SAMPLE_S16LE; + + return tsmf_pulse_open_stream(pulse); +} + +static boolean tsmf_pulse_play(ITSMFAudioDevice* audio, uint8* data, uint32 data_size) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + uint8* src; + int len; + int ret; + + DEBUG_DVC("data_size %d", 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_DVC("waiting"); + pa_threaded_mainloop_wait(pulse->mainloop); + } + if (len < 0) + break; + if (len > data_size) + len = data_size; + ret = pa_stream_write(pulse->stream, src, len, NULL, 0LL, PA_SEEK_RELATIVE); + if (ret < 0) + { + DEBUG_DVC("pa_stream_write failed (%d)", + pa_context_errno(pulse->context)); + break; + } + src += len; + data_size -= len; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + } + xfree(data); + + return True; +} + +static uint64 tsmf_pulse_get_latency(ITSMFAudioDevice* audio) +{ + pa_usec_t usec; + uint64 latency = 0; + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + if (pulse->stream && pa_stream_get_latency(pulse->stream, &usec, NULL) == 0) + { + latency = ((uint64)usec) * 10LL; + } + return latency; +} + +static void 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); +} + +static void tsmf_pulse_free(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + DEBUG_DVC(""); + + tsmf_pulse_close_stream(pulse); + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + xfree(pulse); +} + +ITSMFAudioDevice* TSMFAudioDeviceEntry(void) +{ + TSMFPulseAudioDevice* pulse; + + pulse = xnew(TSMFPulseAudioDevice); + + pulse->iface.Open = tsmf_pulse_open; + pulse->iface.SetFormat = tsmf_pulse_set_format; + pulse->iface.Play = tsmf_pulse_play; + pulse->iface.GetLatency = tsmf_pulse_get_latency; + pulse->iface.Flush = tsmf_pulse_flush; + pulse->iface.Free = tsmf_pulse_free; + + return (ITSMFAudioDevice*) pulse; +} + diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 000000000..c81f1132c --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -0,0 +1 @@ +pkg_check_modules(FFMPEG libavcodec) diff --git a/include/freerdp/plugins/tsmf.h b/include/freerdp/plugins/tsmf.h index 18bc6fb39..6df4964b3 100644 --- a/include/freerdp/plugins/tsmf.h +++ b/include/freerdp/plugins/tsmf.h @@ -56,4 +56,9 @@ struct _RDP_REDRAW_EVENT }; typedef struct _RDP_REDRAW_EVENT RDP_REDRAW_EVENT; +/* RDP_VIDEO_FRAME_EVENT.frame_pixfmt */ +/* http://www.fourcc.org/yuv.php */ +#define RDP_PIXFMT_I420 0x30323449 +#define RDP_PIXFMT_YV12 0x32315659 + #endif /* __TSMF_PLUGIN */ From 74ba2b35151e0dbb4073cd9bcd3b76275c4fe770 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Tue, 20 Sep 2011 16:27:59 +0800 Subject: [PATCH 7/9] tsmf: add XVideo support in xfreerdp. --- client/X11/CMakeLists.txt | 9 + client/X11/xf_tsmf.c | 370 ++++++++++++++++++++++++++++++++++++++ client/X11/xf_tsmf.h | 29 +++ client/X11/xfreerdp.c | 47 +++-- client/X11/xfreerdp.h | 1 + cmake/ConfigOptions.cmake | 1 + cmake/FindXv.cmake | 49 +++++ config.h.in | 1 + 8 files changed, 497 insertions(+), 10 deletions(-) create mode 100644 client/X11/xf_tsmf.c create mode 100644 client/X11/xf_tsmf.h create mode 100644 cmake/FindXv.cmake diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt index 7fb7bb0a8..835c089bf 100644 --- a/client/X11/CMakeLists.txt +++ b/client/X11/CMakeLists.txt @@ -25,6 +25,8 @@ add_executable(xfreerdp xf_gdi.h xf_rail.c xf_rail.h + xf_tsmf.c + xf_tsmf.h xf_event.c xf_event.h xf_keyboard.c @@ -72,6 +74,13 @@ if(Xcursor_FOUND) target_link_libraries(xfreerdp ${Xcursor_LIBRARIES}) endif() +find_suggested_package(Xv) +if(XV_FOUND) + add_definitions(-DWITH_XV) + include_directories(${XV_INCLUDE_DIRS}) + target_link_libraries(xfreerdp ${XV_LIBRARIES}) +endif() + target_link_libraries(xfreerdp freerdp-core) target_link_libraries(xfreerdp freerdp-gdi) target_link_libraries(xfreerdp freerdp-kbd) diff --git a/client/X11/xf_tsmf.c b/client/X11/xf_tsmf.c new file mode 100644 index 000000000..8f51ecf32 --- /dev/null +++ b/client/X11/xf_tsmf.c @@ -0,0 +1,370 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * X11 Video Redirection + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xf_tsmf.h" + +#ifdef WITH_XV + +#include +#include + +typedef struct xf_xv_context xfXvContext; + +struct xf_xv_context +{ + long xv_port; + Atom xv_colorkey_atom; + int xv_image_size; + int xv_shmid; + char* xv_shmaddr; + uint32* xv_pixfmts; +}; + +#ifdef WITH_DEBUG_XV +#define DEBUG_XV(fmt, ...) DEBUG_CLASS(XV, fmt, ## __VA_ARGS__) +#else +#define DEBUG_XV(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__) +#endif + +void xf_tsmf_init(xfInfo* xfi, long xv_port) +{ + int ret; + unsigned int i; + unsigned int version; + unsigned int release; + unsigned int event_base; + unsigned int error_base; + unsigned int request_base; + unsigned int num_adaptors; + xfXvContext* xv; + XvAdaptorInfo* ai; + XvAttribute* attr; + XvImageFormatValues* fo; + + xv = xnew(xfXvContext); + xfi->xv_context = xv; + + xv->xv_colorkey_atom = None; + xv->xv_image_size = 0; + xv->xv_port = xv_port; + + if (!XShmQueryExtension(xfi->display)) + { + DEBUG_XV("no shmem available."); + return; + } + + ret = XvQueryExtension(xfi->display, &version, &release, &request_base, &event_base, &error_base); + if (ret != Success) + { + DEBUG_XV("XvQueryExtension failed %d.", ret); + return; + } + DEBUG_XV("version %u release %u", version, release); + + ret = XvQueryAdaptors(xfi->display, DefaultRootWindow(xfi->display), + &num_adaptors, &ai); + if (ret != Success) + { + DEBUG_XV("XvQueryAdaptors failed %d.", ret); + return; + } + + for (i = 0; i < num_adaptors; i++) + { + DEBUG_XV("adapter port %ld-%ld (%s)", ai[i].base_id, + ai[i].base_id + ai[i].num_ports - 1, ai[i].name); + if (xv->xv_port == 0 && i == num_adaptors - 1) + xv->xv_port = ai[i].base_id; + } + + if (num_adaptors > 0) + XvFreeAdaptorInfo(ai); + + if (xv->xv_port == 0) + { + DEBUG_XV("no adapter selected, video frames will not be processed."); + return; + } + DEBUG_XV("selected %ld", xv->xv_port); + + attr = XvQueryPortAttributes(xfi->display, xv->xv_port, &ret); + for (i = 0; i < (unsigned int)ret; i++) + { + if (strcmp(attr[i].name, "XV_COLORKEY") == 0) + { + xv->xv_colorkey_atom = XInternAtom(xfi->display, "XV_COLORKEY", False); + XvSetPortAttribute(xfi->display, xv->xv_port, xv->xv_colorkey_atom, attr[i].min_value + 1); + break; + } + } + XFree(attr); + +#ifdef WITH_DEBUG_XV + printf("xf_tsmf_init: pixel format "); +#endif + fo = XvListImageFormats(xfi->display, xv->xv_port, &ret); + if (ret > 0) + { + xv->xv_pixfmts = (uint32*) xzalloc((ret + 1) * sizeof(uint32)); + for (i = 0; i < ret; i++) + { + xv->xv_pixfmts[i] = fo[i].id; +#ifdef WITH_DEBUG_XV + printf("%c%c%c%c ", ((char*)(xv->xv_pixfmts + i))[0], ((char*)(xv->xv_pixfmts + i))[1], + ((char*)(xv->xv_pixfmts + i))[2], ((char*)(xv->xv_pixfmts + i))[3]); +#endif + } + xv->xv_pixfmts[i] = 0; + } + XFree(fo); +#ifdef WITH_DEBUG_XV + printf("\n"); +#endif +} + +void xf_tsmf_uninit(xfInfo* xfi) +{ + xfXvContext* xv = (xfXvContext*) xfi->xv_context; + + if (xv) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, NULL); + } + if (xv->xv_pixfmts) + { + xfree(xv->xv_pixfmts); + xv->xv_pixfmts = NULL; + } + xfree(xv); + xfi->xv_context = NULL; + } +} + +static boolean +xf_tsmf_is_format_supported(xfXvContext* xv, uint32 pixfmt) +{ + int i; + + if (!xv->xv_pixfmts) + return False; + + for (i = 0; xv->xv_pixfmts[i]; i++) + { + if (xv->xv_pixfmts[i] == pixfmt) + return True; + } + + return False; +} + +static void xf_process_tsmf_video_frame_event(xfInfo* xfi, RDP_VIDEO_FRAME_EVENT* vevent) +{ + int i; + uint8* data1; + uint8* data2; + uint32 pixfmt; + XvImage * image; + int colorkey = 0; + XShmSegmentInfo shminfo; + xfXvContext* xv = (xfXvContext*) xfi->xv_context; + + if (xv->xv_port == 0) + return; + + /* In case the player is minimized */ + if (vevent->x < -2048 || vevent->y < -2048 || vevent->num_visible_rects <= 0) + return; + + if (xv->xv_colorkey_atom != None) + { + XvGetPortAttribute(xfi->display, xv->xv_port, xv->xv_colorkey_atom, &colorkey); + XSetFunction(xfi->display, xfi->gc, GXcopy); + XSetFillStyle(xfi->display, xfi->gc, FillSolid); + XSetForeground(xfi->display, xfi->gc, colorkey); + for (i = 0; i < vevent->num_visible_rects; i++) + { + XFillRectangle(xfi->display, xfi->window->handle, xfi->gc, + vevent->x + vevent->visible_rects[i].x, + vevent->y + vevent->visible_rects[i].y, + vevent->visible_rects[i].width, + vevent->visible_rects[i].height); + } + } + + pixfmt = vevent->frame_pixfmt; + image = XvShmCreateImage(xfi->display, xv->xv_port, + pixfmt, 0, vevent->frame_width, vevent->frame_height, &shminfo); + if (xv->xv_image_size != image->data_size) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, NULL); + } + xv->xv_image_size = image->data_size; + xv->xv_shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0777); + xv->xv_shmaddr = shmat(xv->xv_shmid, 0, 0); + } + shminfo.shmid = xv->xv_shmid; + shminfo.shmaddr = image->data = xv->xv_shmaddr; + shminfo.readOnly = False; + + if (!XShmAttach(xfi->display, &shminfo)) + { + XFree(image); + DEBUG_XV("XShmAttach failed."); + return; + } + + /* The video driver may align each line to a different size + and we need to convert our original image data. */ + switch (pixfmt) + { + case RDP_PIXFMT_I420: + case RDP_PIXFMT_YV12: + if (!xf_tsmf_is_format_supported(xv, RDP_PIXFMT_I420) && + !xf_tsmf_is_format_supported(xv, RDP_PIXFMT_YV12)) + { + DEBUG_XV("pixel format 0x%X not supported by hardware.", pixfmt); + break; + } + /* Y */ + if (image->pitches[0] == vevent->frame_width) + { + memcpy(image->data + image->offsets[0], + vevent->frame_data, + vevent->frame_width * vevent->frame_height); + } + else + { + for (i = 0; i < vevent->frame_height; i++) + { + memcpy(image->data + image->offsets[0] + i * image->pitches[0], + vevent->frame_data + i * vevent->frame_width, + vevent->frame_width); + } + } + /* UV */ + /* Conversion between I420 and YV12 is to simply swap U and V */ + if (xf_tsmf_is_format_supported(xv, pixfmt)) + { + data1 = vevent->frame_data + vevent->frame_width * vevent->frame_height; + data2 = vevent->frame_data + vevent->frame_width * vevent->frame_height + + vevent->frame_width * vevent->frame_height / 4; + } + else + { + data2 = vevent->frame_data + vevent->frame_width * vevent->frame_height; + data1 = vevent->frame_data + vevent->frame_width * vevent->frame_height + + vevent->frame_width * vevent->frame_height / 4; + image->id = pixfmt == RDP_PIXFMT_I420 ? RDP_PIXFMT_YV12 : RDP_PIXFMT_I420; + } + if (image->pitches[1] * 2 == vevent->frame_width) + { + memcpy(image->data + image->offsets[1], + data1, + vevent->frame_width * vevent->frame_height / 4); + memcpy(image->data + image->offsets[2], + data2, + vevent->frame_width * vevent->frame_height / 4); + } + else + { + for (i = 0; i < vevent->frame_height / 2; i++) + { + memcpy(image->data + image->offsets[1] + i * image->pitches[1], + data1 + i * vevent->frame_width / 2, + vevent->frame_width / 2); + memcpy(image->data + image->offsets[2] + i * image->pitches[2], + data2 + i * vevent->frame_width / 2, + vevent->frame_width / 2); + } + } + break; + + default: + memcpy(image->data, vevent->frame_data, image->data_size <= vevent->frame_size ? + image->data_size : vevent->frame_size); + break; + } + + XvShmPutImage(xfi->display, xv->xv_port, xfi->window->handle, xfi->gc, image, + 0, 0, image->width, image->height, + vevent->x, vevent->y, vevent->width, vevent->height, False); + XSync(xfi->display, False); + + XShmDetach(xfi->display, &shminfo); + XFree(image); +} + +static void xf_process_tsmf_redraw_event(xfInfo* xfi, RDP_REDRAW_EVENT* revent) +{ + XSetFunction(xfi->display, xfi->gc, GXcopy); + XSetFillStyle(xfi->display, xfi->gc, FillSolid); + XCopyArea(xfi->display, xfi->primary, xfi->window->handle, xfi->gc, + revent->x, revent->y, revent->width, revent->height, revent->x, revent->y); +} + +void xf_process_tsmf_event(xfInfo* xfi, RDP_EVENT* event) +{ + switch (event->event_type) + { + case RDP_EVENT_TYPE_TSMF_VIDEO_FRAME: + xf_process_tsmf_video_frame_event(xfi, (RDP_VIDEO_FRAME_EVENT*) event); + break; + + case RDP_EVENT_TYPE_TSMF_REDRAW: + xf_process_tsmf_redraw_event(xfi, (RDP_REDRAW_EVENT*) event); + break; + + } +} + +#else /* WITH_XV */ + +void xf_tsmf_init(xfInfo* xfi, long xv_port) +{ +} + +void xf_tsmf_uninit(xfInfo* xfi) +{ +} + +void xf_process_tsmf_event(xfInfo* xfi, RDP_EVENT* event) +{ +} + +#endif /* WITH_XV */ diff --git a/client/X11/xf_tsmf.h b/client/X11/xf_tsmf.h new file mode 100644 index 000000000..09083e263 --- /dev/null +++ b/client/X11/xf_tsmf.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * X11 Video Redirection + * + * 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 __XF_TSMF_H +#define __XF_TSMF_H + +#include "xfreerdp.h" + +void xf_tsmf_init(xfInfo* xfi, long xv_port); +void xf_tsmf_uninit(xfInfo* xfi); +void xf_process_tsmf_event(xfInfo* xfi, RDP_EVENT* event); + +#endif /* __XF_TSMF_H */ diff --git a/client/X11/xfreerdp.c b/client/X11/xfreerdp.c index a572b28f3..5e2e9ad7a 100644 --- a/client/X11/xfreerdp.c +++ b/client/X11/xfreerdp.c @@ -46,6 +46,7 @@ #include "xf_gdi.h" #include "xf_rail.h" +#include "xf_tsmf.h" #include "xf_event.h" #include "xf_monitor.h" #include "xf_keyboard.h" @@ -55,6 +56,8 @@ freerdp_sem g_sem; static int g_thread_count = 0; +static long xv_port = 0; + struct thread_data { freerdp* instance; @@ -463,11 +466,15 @@ boolean xf_post_connect(freerdp* instance) freerdp_chanman_post_connect(GET_CHANMAN(instance), instance); + xf_tsmf_init(xfi, xv_port); + return True; } int xf_process_ui_args(rdpSettings* settings, const char* opt, const char* val, void* user_data) { + int argc = 0; + if (strcmp("--kbd-list", opt) == 0) { int i; @@ -493,8 +500,13 @@ int xf_process_ui_args(rdpSettings* settings, const char* opt, const char* val, exit(0); } + else if (strcmp("--xv-port", opt) == 0) + { + xv_port = atoi(val); + argc = 2; + } - return 1; + return argc; } int xf_process_plugin_args(rdpSettings* settings, const char* name, RDP_PLUGIN_DATA* plugin_data, void* user_data) @@ -512,7 +524,7 @@ int xf_receive_channel_data(freerdp* instance, int channelId, uint8* data, int s return freerdp_chanman_data(instance, channelId, data, size, flags, total_size); } -void xf_process_cb_sync_event(rdpChanMan* chanman, freerdp* instance) +void xf_process_cb_sync_event(xfInfo* xfi, rdpChanMan* chanman) { RDP_EVENT* event; RDP_CB_FORMAT_LIST_EVENT* format_list_event; @@ -525,6 +537,19 @@ void xf_process_cb_sync_event(rdpChanMan* chanman, freerdp* instance) freerdp_chanman_send_event(chanman, event); } +void xf_process_cliprdr_event(xfInfo* xfi, rdpChanMan* chanman, RDP_EVENT* event) +{ + switch (event->event_type) + { + case RDP_EVENT_TYPE_CB_SYNC: + xf_process_cb_sync_event(xfi, chanman); + break; + + default: + break; + } +} + void xf_process_channel_event(rdpChanMan* chanman, freerdp* instance) { xfInfo* xfi; @@ -542,18 +567,18 @@ void xf_process_channel_event(rdpChanMan* chanman, freerdp* instance) xf_process_rail_event(xfi, chanman, event); break; + case RDP_EVENT_CLASS_TSMF: + xf_process_tsmf_event(xfi, event); + break; + + case RDP_EVENT_CLASS_CLIPRDR: + xf_process_cliprdr_event(xfi, chanman, event); + break; + default: break; } - switch (event->event_type) - { - case RDP_EVENT_TYPE_CB_SYNC: - xf_process_cb_sync_event(chanman, instance); - break; - default: - break; - } freerdp_event_free(event); } } @@ -590,6 +615,8 @@ void xf_window_free(xfInfo* xfi) xfree(xfi->clrconv); rail_free(xfi->rail); + + xf_tsmf_uninit(xfi); } void xf_free(xfInfo* xfi) diff --git a/client/X11/xfreerdp.h b/client/X11/xfreerdp.h index af09cca5f..089404b98 100644 --- a/client/X11/xfreerdp.h +++ b/client/X11/xfreerdp.h @@ -93,6 +93,7 @@ struct xf_info VIRTUAL_SCREEN vscreen; uint8* bmp_codec_none; void* rfx_context; + void* xv_context; Atom _NET_WM_ICON; Atom _MOTIF_WM_HINTS; diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake index 9715771bc..285410876 100644 --- a/cmake/ConfigOptions.cmake +++ b/cmake/ConfigOptions.cmake @@ -11,6 +11,7 @@ option(WITH_DEBUG_GDI "Print graphics debug messages." OFF) option(WITH_DEBUG_RFX "Print RemoteFX debug messages." OFF) option(WITH_DEBUG_X11 "Print X11 Client debug messages" OFF) option(WITH_DEBUG_RAIL "Print RemoteApp debug messages" OFF) +option(WITH_DEBUG_XV "Print XVideo debug messages" OFF) option(WITH_MANPAGES "Generate manpages." ON) option(WITH_PROFILER "Compile profiler." OFF) option(WITH_SSE2 "Use SSE2 optimization." OFF) diff --git a/cmake/FindXv.cmake b/cmake/FindXv.cmake new file mode 100644 index 000000000..b95fb99f1 --- /dev/null +++ b/cmake/FindXv.cmake @@ -0,0 +1,49 @@ +# - Find Xv +# Find the Xv libraries +# +# This module defines the following variables: +# XV_FOUND - True if XV_INCLUDE_DIR & XV_LIBRARY are found +# XV_LIBRARIES - Set when XV_LIBRARY is found +# XV_INCLUDE_DIRS - Set when XV_INCLUDE_DIR is found +# +# XV_INCLUDE_DIR - where to find Xv.h, etc. +# XV_LIBRARY - the Xv library +# + +#============================================================================= +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. +#============================================================================= + +find_path(XV_INCLUDE_DIR NAMES Xv.h + PATH_SUFFIXES X11/extensions + DOC "The Xv include directory" +) + +find_library(XV_LIBRARY NAMES Xv + DOC "The Xv library" +) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(XV DEFAULT_MSG XV_LIBRARY XV_INCLUDE_DIR) + +if(XV_FOUND) + set( XV_LIBRARIES ${XV_LIBRARY} ) + set( XV_INCLUDE_DIRS ${XV_INCLUDE_DIR} ) +endif() + +mark_as_advanced(XV_INCLUDE_DIR XV_LIBRARY) + diff --git a/config.h.in b/config.h.in index 9eea02667..9b7c01f26 100644 --- a/config.h.in +++ b/config.h.in @@ -33,5 +33,6 @@ #cmakedefine WITH_SSE2 #cmakedefine WITH_DEBUG_X11 #cmakedefine WITH_DEBUG_RAIL +#cmakedefine WITH_DEBUG_XV #endif From 27a82cccfa7ec28ff8e5940f05dc25c754ef6b80 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Tue, 20 Sep 2011 20:15:15 +0800 Subject: [PATCH 8/9] libfreerdp-gdi: fix segfault in fast_index. --- libfreerdp-core/orders.c | 4 ++-- libfreerdp-gdi/gdi.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libfreerdp-core/orders.c b/libfreerdp-core/orders.c index 079c8f1a3..6b99f3057 100644 --- a/libfreerdp-core/orders.c +++ b/libfreerdp-core/orders.c @@ -443,8 +443,8 @@ INLINE uint16 update_read_glyph_fragments(STREAM* s, GLYPH_FRAGMENT** fragments, array_mark = NULL; stream_end = s->p + size; stream_get_mark(s, stream_start); - offsets = (uint8**) xmalloc(size / 2); - lengths = (uint16*) xmalloc(size / 2); + offsets = (uint8**) xmalloc(sizeof(uint8*) * size / 2); + lengths = (uint16*) xmalloc(sizeof(uint16) * size / 2); operations = (uint8*) xmalloc(size / 2); while (s->p < stream_end) diff --git a/libfreerdp-gdi/gdi.c b/libfreerdp-gdi/gdi.c index 39c121ab4..22f0e6b8d 100644 --- a/libfreerdp-gdi/gdi.c +++ b/libfreerdp-gdi/gdi.c @@ -724,6 +724,9 @@ void gdi_fast_index(rdpUpdate* update, FAST_INDEX_ORDER* fast_index) bmp = bmps[j]; glyph = glyphs[j]; + if (bmp == NULL || glyph == NULL) + continue; + gdi_BitBlt(gdi->drawing->hdc, glyph->x + x, glyph->y + y, bmp->bitmap->width, bmp->bitmap->height, bmp->hdc, 0, 0, GDI_DSPDxax); From 6c914cd60d64793e8d1652bcb631e2a4272c5d16 Mon Sep 17 00:00:00 2001 From: Vic Lee Date: Tue, 20 Sep 2011 22:03:05 +0800 Subject: [PATCH 9/9] tsmf: fix incorrect data realignment. --- channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c b/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c index a41187fb0..c9ad77243 100644 --- a/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c +++ b/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c @@ -346,7 +346,7 @@ static boolean tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const uint8* data /* Ensure enough space for decoding */ if (mdecoder->decoded_size_max - mdecoder->decoded_size < AVCODEC_MAX_AUDIO_FRAME_SIZE) { - mdecoder->decoded_size_max *= 2; + mdecoder->decoded_size_max = mdecoder->decoded_size_max * 2 + 16; mdecoder->decoded_data = xrealloc(mdecoder->decoded_data, mdecoder->decoded_size_max); dst = (uint8*) (((uintptr_t)mdecoder->decoded_data + 15) & ~ 0x0F); if (dst - mdecoder->decoded_data != dst_offset) @@ -391,7 +391,7 @@ static boolean tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const uint8* data else if (dst_offset) { /* move the aligned decoded data to original place */ - memmove(mdecoder->decoded_data, dst, mdecoder->decoded_size); + memmove(mdecoder->decoded_data, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); } DEBUG_DVC("data_size %d decoded_size %d",