summaryrefslogtreecommitdiffstats
path: root/channels/tsmf/client/gstreamer
diff options
context:
space:
mode:
Diffstat (limited to 'channels/tsmf/client/gstreamer')
-rw-r--r--channels/tsmf/client/gstreamer/CMakeLists.txt76
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_X11.c500
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_gstreamer.c1054
-rw-r--r--channels/tsmf/client/gstreamer/tsmf_platform.h85
4 files changed, 1715 insertions, 0 deletions
diff --git a/channels/tsmf/client/gstreamer/CMakeLists.txt b/channels/tsmf/client/gstreamer/CMakeLists.txt
new file mode 100644
index 0000000..9490cc0
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/CMakeLists.txt
@@ -0,0 +1,76 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script for gstreamer subsystem
+#
+# (C) Copyright 2012 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+define_channel_client_subsystem("tsmf" "gstreamer" "decoder")
+
+if(NOT gstreamer_FOUND)
+ message(FATAL_ERROR "GStreamer library not found, but required for TSMF module.")
+endif()
+
+set(SRC "tsmf_gstreamer.c")
+
+pkg_check_modules(gstreamerbase gstreamer-base-1.0 REQUIRED)
+pkg_check_modules(gstreamervideo gstreamer-video-1.0 REQUIRED)
+pkg_check_modules(gstreamerapp gstreamer-app-1.0 REQUIRED)
+
+set(LIBS
+ ${gstreamer_LIBRARIES}
+ ${gstreamerbase_LIBRARIES}
+ ${gstreamervideo_LIBRARIES}
+ ${gstreamerapp_LIBRARIES}
+)
+include_directories(
+ ${gstreamer_INCLUDE_DIRS}
+ ${gstreamerbase_INCLUDE_DIRS}
+ ${gstreamervideo_INCLUDE_DIRS}
+ ${gstreamerapp_INCLUDE_DIRS}
+)
+
+
+if(ANDROID)
+ set(SRC ${SRC}
+ tsmf_android.c)
+else()
+ find_package(X11 REQUIRED)
+
+ list(APPEND SRC
+ tsmf_X11.c)
+ list(APPEND LIBS
+ ${X11_LIBRARIES}
+ ${X11_Xext_LIB})
+ if (NOT APPLE)
+ list(APPEND LIBS rt)
+ endif()
+
+ if(X11_Xext_FOUND)
+ add_definitions(-DWITH_XEXT=1)
+ endif()
+
+endif()
+
+set(${MODULE_PREFIX}_SRCS
+ "${SRC}"
+)
+
+set(${MODULE_PREFIX}_LIBS
+ ${LIBS}
+ winpr
+)
+
+include_directories(..)
+
+add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
diff --git a/channels/tsmf/client/gstreamer/tsmf_X11.c b/channels/tsmf/client/gstreamer/tsmf_X11.c
new file mode 100644
index 0000000..987e69b
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/tsmf_X11.c
@@ -0,0 +1,500 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - GStreamer Decoder X11 specifics
+ *
+ * (C) Copyright 2014 Thincast Technologies GmbH
+ * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#ifndef __CYGWIN__
+#include <sys/syscall.h>
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <err.h>
+#include <errno.h>
+#include <winpr/thread.h>
+#include <winpr/string.h>
+#include <winpr/platform.h>
+
+#include <gst/gst.h>
+
+#if GST_VERSION_MAJOR > 0
+#include <gst/video/videooverlay.h>
+#else
+#include <gst/interfaces/xoverlay.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/shape.h>
+
+#include <freerdp/channels/tsmf.h>
+
+#include "tsmf_platform.h"
+#include "tsmf_constants.h"
+#include "tsmf_decoder.h"
+
+#if !defined(WITH_XEXT)
+#warning "Building TSMF without shape extension support"
+#endif
+
+struct X11Handle
+{
+ int shmid;
+ int* xfwin;
+#if defined(WITH_XEXT)
+ BOOL has_shape;
+#endif
+ Display* disp;
+ Window subwin;
+ BOOL subwinMapped;
+#if GST_VERSION_MAJOR > 0
+ GstVideoOverlay* overlay;
+#else
+ GstXOverlay* overlay;
+#endif
+ int subwinWidth;
+ int subwinHeight;
+ int subwinX;
+ int subwinY;
+};
+
+static const char* get_shm_id()
+{
+ static char shm_id[128];
+ sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId());
+ return shm_id;
+}
+
+static GstBusSyncReply tsmf_platform_bus_sync_handler(GstBus* bus, GstMessage* message,
+ gpointer user_data)
+{
+ struct X11Handle* hdl;
+
+ TSMFGstreamerDecoder* decoder = user_data;
+
+ if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT)
+ return GST_BUS_PASS;
+
+#if GST_VERSION_MAJOR > 0
+ if (!gst_is_video_overlay_prepare_window_handle_message(message))
+ return GST_BUS_PASS;
+#else
+ if (!gst_structure_has_name(message->structure, "prepare-xwindow-id"))
+ return GST_BUS_PASS;
+#endif
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ if (hdl->subwin)
+ {
+#if GST_VERSION_MAJOR > 0
+ hdl->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message));
+ gst_video_overlay_set_window_handle(hdl->overlay, hdl->subwin);
+ gst_video_overlay_handle_events(hdl->overlay, FALSE);
+#else
+ hdl->overlay = GST_X_OVERLAY(GST_MESSAGE_SRC(message));
+#if GST_CHECK_VERSION(0, 10, 31)
+ gst_x_overlay_set_window_handle(hdl->overlay, hdl->subwin);
+#else
+ gst_x_overlay_set_xwindow_id(hdl->overlay, hdl->subwin);
+#endif
+ gst_x_overlay_handle_events(hdl->overlay, TRUE);
+#endif
+
+ if (hdl->subwinWidth != -1 && hdl->subwinHeight != -1 && hdl->subwinX != -1 &&
+ hdl->subwinY != -1)
+ {
+#if GST_VERSION_MAJOR > 0
+ if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth,
+ hdl->subwinHeight))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_video_overlay_expose(hdl->overlay);
+#else
+ if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth,
+ hdl->subwinHeight))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_x_overlay_expose(hdl->overlay);
+#endif
+ XLockDisplay(hdl->disp);
+ XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth,
+ hdl->subwinHeight);
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+ }
+ else
+ {
+ g_warning("Window was not available before retrieving the overlay!");
+ }
+
+ gst_message_unref(message);
+
+ return GST_BUS_DROP;
+}
+
+const char* tsmf_platform_get_video_sink(void)
+{
+ return "autovideosink";
+}
+
+const char* tsmf_platform_get_audio_sink(void)
+{
+ return "autoaudiosink";
+}
+
+int tsmf_platform_create(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+
+ if (!decoder)
+ return -1;
+
+ if (decoder->platform)
+ return -1;
+
+ hdl = calloc(1, sizeof(struct X11Handle));
+ if (!hdl)
+ {
+ WLog_ERR(TAG, "Could not allocate handle.");
+ return -1;
+ }
+
+ decoder->platform = hdl;
+ hdl->shmid = shm_open(get_shm_id(), (O_RDWR | O_CREAT), (PROT_READ | PROT_WRITE));
+ if (hdl->shmid == -1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "failed to get access to shared memory - shmget(%s): %i - %s", get_shm_id(),
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return -2;
+ }
+
+ hdl->xfwin = mmap(0, sizeof(void*), PROT_READ | PROT_WRITE, MAP_SHARED, hdl->shmid, 0);
+ if (hdl->xfwin == MAP_FAILED)
+ {
+ WLog_ERR(TAG, "shmat failed!");
+ return -3;
+ }
+
+ hdl->disp = XOpenDisplay(NULL);
+ if (!hdl->disp)
+ {
+ WLog_ERR(TAG, "Failed to open display");
+ return -4;
+ }
+
+ hdl->subwinMapped = FALSE;
+ hdl->subwinX = -1;
+ hdl->subwinY = -1;
+ hdl->subwinWidth = -1;
+ hdl->subwinHeight = -1;
+
+ return 0;
+}
+
+int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder)
+{
+ if (!decoder)
+ return -1;
+
+ if (decoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ }
+
+ return 0;
+}
+
+int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder)
+{
+ GstBus* bus;
+
+ if (!decoder)
+ return -1;
+
+ if (!decoder->pipe)
+ return -1;
+
+ bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipe));
+
+#if GST_VERSION_MAJOR > 0
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder, NULL);
+#else
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder);
+#endif
+
+ if (!bus)
+ {
+ WLog_ERR(TAG, "gst_pipeline_get_bus failed!");
+ return 1;
+ }
+
+ gst_object_unref(bus);
+
+ return 0;
+}
+
+int tsmf_platform_free(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl = decoder->platform;
+
+ if (!hdl)
+ return -1;
+
+ if (hdl->disp)
+ XCloseDisplay(hdl->disp);
+
+ if (hdl->xfwin)
+ munmap(0, sizeof(void*));
+
+ if (hdl->shmid >= 0)
+ close(hdl->shmid);
+
+ free(hdl);
+ decoder->platform = NULL;
+
+ return 0;
+}
+
+int tsmf_window_create(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+
+ if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO)
+ {
+ decoder->ready = TRUE;
+ return -3;
+ }
+ else
+ {
+ if (!decoder)
+ return -1;
+
+ if (!decoder->platform)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ if (!hdl->subwin)
+ {
+ XLockDisplay(hdl->disp);
+ hdl->subwin = XCreateSimpleWindow(hdl->disp, *(int*)hdl->xfwin, 0, 0, 1, 1, 0, 0, 0);
+ XUnlockDisplay(hdl->disp);
+
+ if (!hdl->subwin)
+ {
+ WLog_ERR(TAG, "Could not create subwindow!");
+ }
+ }
+
+ tsmf_window_map(decoder);
+
+ decoder->ready = TRUE;
+#if defined(WITH_XEXT)
+ int event, error;
+ XLockDisplay(hdl->disp);
+ hdl->has_shape = XShapeQueryExtension(hdl->disp, &event, &error);
+ XUnlockDisplay(hdl->disp);
+#endif
+ }
+
+ return 0;
+}
+
+int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height,
+ int nr_rects, RDP_RECT* rects)
+{
+ struct X11Handle* hdl;
+
+ if (!decoder)
+ return -1;
+
+ if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO)
+ {
+ return -3;
+ }
+
+ if (!decoder->platform)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+ DEBUG_TSMF("resize: x=%d, y=%d, w=%d, h=%d", x, y, width, height);
+
+ if (hdl->overlay)
+ {
+#if GST_VERSION_MAJOR > 0
+
+ if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_video_overlay_expose(hdl->overlay);
+#else
+ if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height))
+ {
+ WLog_ERR(TAG, "Could not resize overlay!");
+ }
+
+ gst_x_overlay_expose(hdl->overlay);
+#endif
+ }
+
+ if (hdl->subwin)
+ {
+ hdl->subwinX = x;
+ hdl->subwinY = y;
+ hdl->subwinWidth = width;
+ hdl->subwinHeight = height;
+
+ XLockDisplay(hdl->disp);
+ XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth,
+ hdl->subwinHeight);
+
+ /* Unmap the window if there are no visibility rects */
+ if (nr_rects == 0)
+ tsmf_window_unmap(decoder);
+ else
+ tsmf_window_map(decoder);
+
+#if defined(WITH_XEXT)
+ if (hdl->has_shape)
+ {
+ XRectangle* xrects = NULL;
+
+ if (nr_rects == 0)
+ {
+ xrects = calloc(1, sizeof(XRectangle));
+ xrects->x = x;
+ xrects->y = y;
+ xrects->width = width;
+ xrects->height = height;
+ }
+ else
+ {
+ xrects = calloc(nr_rects, sizeof(XRectangle));
+ }
+
+ if (xrects)
+ {
+ for (int i = 0; i < nr_rects; i++)
+ {
+ xrects[i].x = rects[i].x - x;
+ xrects[i].y = rects[i].y - y;
+ xrects[i].width = rects[i].width;
+ xrects[i].height = rects[i].height;
+ }
+
+ XShapeCombineRectangles(hdl->disp, hdl->subwin, ShapeBounding, x, y, xrects,
+ nr_rects, ShapeSet, 0);
+ free(xrects);
+ }
+ }
+#endif
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ return 0;
+}
+
+int tsmf_window_map(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+ if (!decoder)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ /* Only need to map the window if it is not currently mapped */
+ if ((hdl->subwin) && (!hdl->subwinMapped))
+ {
+ XLockDisplay(hdl->disp);
+ XMapWindow(hdl->disp, hdl->subwin);
+ hdl->subwinMapped = TRUE;
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ return 0;
+}
+
+int tsmf_window_unmap(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+ if (!decoder)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ /* only need to unmap window if it is currently mapped */
+ if ((hdl->subwin) && (hdl->subwinMapped))
+ {
+ XLockDisplay(hdl->disp);
+ XUnmapWindow(hdl->disp, hdl->subwin);
+ hdl->subwinMapped = FALSE;
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ return 0;
+}
+
+int tsmf_window_destroy(TSMFGstreamerDecoder* decoder)
+{
+ struct X11Handle* hdl;
+
+ if (!decoder)
+ return -1;
+
+ decoder->ready = FALSE;
+
+ if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO)
+ return -3;
+
+ if (!decoder->platform)
+ return -1;
+
+ hdl = (struct X11Handle*)decoder->platform;
+
+ if (hdl->subwin)
+ {
+ XLockDisplay(hdl->disp);
+ XDestroyWindow(hdl->disp, hdl->subwin);
+ XSync(hdl->disp, FALSE);
+ XUnlockDisplay(hdl->disp);
+ }
+
+ hdl->overlay = NULL;
+ hdl->subwin = 0;
+ hdl->subwinMapped = FALSE;
+ hdl->subwinX = -1;
+ hdl->subwinY = -1;
+ hdl->subwinWidth = -1;
+ hdl->subwinHeight = -1;
+ return 0;
+}
diff --git a/channels/tsmf/client/gstreamer/tsmf_gstreamer.c b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c
new file mode 100644
index 0000000..9087876
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c
@@ -0,0 +1,1054 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - GStreamer Decoder
+ *
+ * (C) Copyright 2012 HP Development Company, LLC
+ * (C) Copyright 2014 Thincast Technologies GmbH
+ * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <winpr/string.h>
+#include <winpr/platform.h>
+
+#include <gst/gst.h>
+
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+
+#include "tsmf_constants.h"
+#include "tsmf_decoder.h"
+#include "tsmf_platform.h"
+
+/* 1 second = 10,000,000 100ns units*/
+#define SEEK_TOLERANCE 10 * 1000 * 1000
+
+static BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder);
+static void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder);
+static int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder,
+ GstState desired_state);
+static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder);
+
+static const char* get_type(TSMFGstreamerDecoder* mdecoder)
+{
+ if (!mdecoder)
+ return NULL;
+
+ switch (mdecoder->media_type)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ return "VIDEO";
+ case TSMF_MAJOR_TYPE_AUDIO:
+ return "AUDIO";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void cb_child_added(GstChildProxy* child_proxy, GObject* object,
+ TSMFGstreamerDecoder* mdecoder)
+{
+ DEBUG_TSMF("NAME: %s", G_OBJECT_TYPE_NAME(object));
+
+ if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXvImageSink") ||
+ !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXImageSink") ||
+ !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstFluVAAutoSink"))
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */
+ g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */
+ g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */
+ }
+
+ else if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstAlsaSink") ||
+ !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink"))
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */
+ g_object_set(G_OBJECT(object), "slave-method", 1, NULL);
+ g_object_set(G_OBJECT(object), "buffer-time", (gint64)20000, NULL); /* microseconds */
+ g_object_set(G_OBJECT(object), "drift-tolerance", (gint64)20000, NULL); /* microseconds */
+ g_object_set(G_OBJECT(object), "latency-time", (gint64)10000, NULL); /* microseconds */
+ g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */
+ g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */
+ }
+}
+
+static void tsmf_gstreamer_enough_data(GstAppSrc* src, gpointer user_data)
+{
+ TSMFGstreamerDecoder* mdecoder = user_data;
+ (void)mdecoder;
+ DEBUG_TSMF("%s", get_type(mdecoder));
+}
+
+static void tsmf_gstreamer_need_data(GstAppSrc* src, guint length, gpointer user_data)
+{
+ TSMFGstreamerDecoder* mdecoder = user_data;
+ (void)mdecoder;
+ DEBUG_TSMF("%s length=%u", get_type(mdecoder), length);
+}
+
+static gboolean tsmf_gstreamer_seek_data(GstAppSrc* src, guint64 offset, gpointer user_data)
+{
+ TSMFGstreamerDecoder* mdecoder = user_data;
+ (void)mdecoder;
+ DEBUG_TSMF("%s offset=%" PRIu64 "", get_type(mdecoder), offset);
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_change_volume(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder || !mdecoder->pipe)
+ return TRUE;
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ return TRUE;
+
+ mdecoder->gstMuted = (BOOL)muted;
+ DEBUG_TSMF("mute=[%" PRId32 "]", mdecoder->gstMuted);
+ mdecoder->gstVolume = (double)newVolume / (double)10000;
+ DEBUG_TSMF("gst_new_vol=[%f]", mdecoder->gstVolume);
+
+ if (!mdecoder->volume)
+ return TRUE;
+
+ if (!G_IS_OBJECT(mdecoder->volume))
+ return TRUE;
+
+ g_object_set(mdecoder->volume, "mute", mdecoder->gstMuted, NULL);
+ g_object_set(mdecoder->volume, "volume", mdecoder->gstVolume, NULL);
+
+ return TRUE;
+}
+
+static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp)
+{
+ /*
+ * Convert Microsoft 100ns timestamps to Gstreamer 1ns units.
+ */
+ return (GstClockTime)(ms_timestamp * 100);
+}
+
+int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, GstState desired_state)
+{
+ GstStateChangeReturn state_change;
+ const char* name;
+ const char* sname = get_type(mdecoder);
+
+ if (!mdecoder)
+ return 0;
+
+ if (!mdecoder->pipe)
+ return 0; /* Just in case this is called during startup or shutdown when we don't expect it
+ */
+
+ if (desired_state == mdecoder->state)
+ return 0; /* Redundant request - Nothing to do */
+
+ name = gst_element_state_get_name(desired_state); /* For debug */
+ DEBUG_TSMF("%s to %s", sname, name);
+ state_change = gst_element_set_state(mdecoder->pipe, desired_state);
+
+ if (state_change == GST_STATE_CHANGE_FAILURE)
+ {
+ WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_FAILURE.", sname, name);
+ }
+ else if (state_change == GST_STATE_CHANGE_ASYNC)
+ {
+ WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_ASYNC.", sname, name);
+ mdecoder->state = desired_state;
+ }
+ else
+ {
+ mdecoder->state = desired_state;
+ }
+
+ return 0;
+}
+
+static GstBuffer* tsmf_get_buffer_from_data(const void* raw_data, gsize size)
+{
+ GstBuffer* buffer;
+ gpointer data;
+
+ if (!raw_data)
+ return NULL;
+
+ if (size < 1)
+ return NULL;
+
+ data = g_malloc(size);
+
+ if (!data)
+ {
+ WLog_ERR(TAG, "Could not allocate %" G_GSIZE_FORMAT " bytes of data.", size);
+ return NULL;
+ }
+
+ CopyMemory(data, raw_data, size);
+
+#if GST_VERSION_MAJOR > 0
+ buffer = gst_buffer_new_wrapped(data, size);
+#else
+ buffer = gst_buffer_new();
+
+ if (!buffer)
+ {
+ WLog_ERR(TAG, "Could not create GstBuffer");
+ free(data);
+ return NULL;
+ }
+
+ GST_BUFFER_MALLOCDATA(buffer) = data;
+ GST_BUFFER_SIZE(buffer) = size;
+ GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer);
+#endif
+
+ return buffer;
+}
+
+static BOOL tsmf_gstreamer_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder)
+ return FALSE;
+
+ DEBUG_TSMF("");
+
+ switch (media_type->MajorType)
+ {
+ case TSMF_MAJOR_TYPE_VIDEO:
+ mdecoder->media_type = TSMF_MAJOR_TYPE_VIDEO;
+ break;
+ case TSMF_MAJOR_TYPE_AUDIO:
+ mdecoder->media_type = TSMF_MAJOR_TYPE_AUDIO;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (media_type->SubType)
+ {
+ case TSMF_SUB_TYPE_WVC1:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
+ G_TYPE_INT, 3,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WVC1",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'V', 'C', '1'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ 1, 1, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP4S:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-divx", "divxversion", G_TYPE_INT, 5, "bitrate", G_TYPE_UINT,
+ media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "MP42",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP42:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 42, "bitrate", G_TYPE_UINT,
+ media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "MP42",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP43:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 43, "bitrate", G_TYPE_UINT,
+ media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "MP43",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '3'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_M4S2:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/mpeg", "mpegversion", G_TYPE_INT, 4, "width", G_TYPE_INT, media_type->Width,
+ "height", G_TYPE_INT, media_type->Height,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "M4S2",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', '4', 'S', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMA9:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-wma", "wmaversion", G_TYPE_INT, 3, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
+ G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
+ media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMA1:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-wma", "wmaversion", G_TYPE_INT, 1, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
+ G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
+ media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMA2:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-wma", "wmaversion", G_TYPE_INT, 2, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth",
+ G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT,
+ media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP3:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT,
+ 3, "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
+ "channels", G_TYPE_INT, media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMV1:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
+ G_TYPE_INT, 1,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WMV1",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '1'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMV2:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height, "wmvversion", G_TYPE_INT, 2,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WMV2",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '2'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ 1, 1, NULL);
+ break;
+ case TSMF_SUB_TYPE_WMV3:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion",
+ G_TYPE_INT, 3,
+#if GST_VERSION_MAJOR > 0
+ "format", G_TYPE_STRING, "WMV3",
+#else
+ "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '3'),
+#endif
+ "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+ 1, 1, NULL);
+ break;
+ case TSMF_SUB_TYPE_AVC1:
+ case TSMF_SUB_TYPE_H264:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-h264", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT,
+ media_type->Height, "framerate", GST_TYPE_FRACTION,
+ media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator,
+ "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, "stream-format", G_TYPE_STRING,
+ "byte-stream", "alignment", G_TYPE_STRING, "nal", NULL);
+ break;
+ case TSMF_SUB_TYPE_AC3:
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/x-ac3", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
+ "channels", G_TYPE_INT, media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_AAC:
+
+ /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data
+ is at the end of it. See
+ http://msdn.microsoft.com/en-us/library/dd757806.aspx */
+ if (media_type->ExtraData)
+ {
+ if (media_type->ExtraDataSize < 12)
+ return FALSE;
+ media_type->ExtraData += 12;
+ media_type->ExtraDataSize -= 12;
+ }
+
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "audio/mpeg", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator,
+ "channels", G_TYPE_INT, media_type->Channels, "mpegversion", G_TYPE_INT, 4,
+ "framed", G_TYPE_BOOLEAN, TRUE, "stream-format", G_TYPE_STRING, "raw", NULL);
+ break;
+ case TSMF_SUB_TYPE_MP1A:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "channels",
+ G_TYPE_INT, media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP1V:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 1, "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height,
+ "systemstream", G_TYPE_BOOLEAN, FALSE, NULL);
+ break;
+ case TSMF_SUB_TYPE_YUY2:
+#if GST_VERSION_MAJOR > 0
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-raw", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, NULL);
+#else
+ mdecoder->gst_caps = gst_caps_new_simple(
+ "video/x-raw-yuv", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT,
+ media_type->Width, "height", G_TYPE_INT, media_type->Height, "framerate",
+ GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator,
+ media_type->SamplesPerSecond.Denominator, NULL);
+#endif
+ break;
+ case TSMF_SUB_TYPE_MP2V:
+ mdecoder->gst_caps = gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 2,
+ "systemstream", G_TYPE_BOOLEAN, FALSE, NULL);
+ break;
+ case TSMF_SUB_TYPE_MP2A:
+ mdecoder->gst_caps =
+ gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "rate", G_TYPE_INT,
+ media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT,
+ media_type->Channels, NULL);
+ break;
+ case TSMF_SUB_TYPE_FLAC:
+ mdecoder->gst_caps = gst_caps_new_simple("audio/x-flac", "", NULL);
+ break;
+ default:
+ WLog_ERR(TAG, "unknown format:(%d).", media_type->SubType);
+ return FALSE;
+ }
+
+ if (media_type->ExtraDataSize > 0)
+ {
+ GstBuffer* buffer;
+ DEBUG_TSMF("Extra data available (%" PRIu32 ")", media_type->ExtraDataSize);
+ buffer = tsmf_get_buffer_from_data(media_type->ExtraData, media_type->ExtraDataSize);
+
+ if (!buffer)
+ {
+ WLog_ERR(TAG, "could not allocate GstBuffer!");
+ return FALSE;
+ }
+
+ gst_caps_set_simple(mdecoder->gst_caps, "codec_data", GST_TYPE_BUFFER, buffer, NULL);
+ }
+
+ DEBUG_TSMF("%p format '%s'", (void*)mdecoder, gst_caps_to_string(mdecoder->gst_caps));
+ tsmf_platform_set_format(mdecoder);
+
+ /* Create the pipeline... */
+ if (!tsmf_gstreamer_pipeline_build(mdecoder))
+ return FALSE;
+
+ return TRUE;
+}
+
+void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder)
+{
+ if (!mdecoder || !mdecoder->pipe)
+ return;
+
+ if (mdecoder->pipe && GST_OBJECT_REFCOUNT_VALUE(mdecoder->pipe) > 0)
+ {
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL);
+ gst_object_unref(mdecoder->pipe);
+ }
+
+ mdecoder->ready = FALSE;
+ mdecoder->paused = FALSE;
+
+ mdecoder->pipe = NULL;
+ mdecoder->src = NULL;
+ mdecoder->queue = NULL;
+}
+
+BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder)
+{
+#if GST_VERSION_MAJOR > 0
+ const char* video =
+ "appsrc name=videosource ! queue2 name=videoqueue ! decodebin name=videodecoder !";
+ const char* audio =
+ "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin name=audiodecoder ! "
+ "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !";
+#else
+ const char* video =
+ "appsrc name=videosource ! queue2 name=videoqueue ! decodebin2 name=videodecoder !";
+ const char* audio =
+ "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin2 name=audiodecoder ! "
+ "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !";
+#endif
+ char pipeline[1024];
+
+ if (!mdecoder)
+ return FALSE;
+
+ /* TODO: Construction of the pipeline from a string allows easy overwrite with arguments.
+ * The only fixed elements necessary are appsrc and the volume element for audio streams.
+ * The rest could easily be provided in gstreamer pipeline notation from command line. */
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ sprintf_s(pipeline, sizeof(pipeline), "%s %s name=videosink", video,
+ tsmf_platform_get_video_sink());
+ else
+ sprintf_s(pipeline, sizeof(pipeline), "%s %s name=audiosink", audio,
+ tsmf_platform_get_audio_sink());
+
+ DEBUG_TSMF("pipeline=%s", pipeline);
+ mdecoder->pipe = gst_parse_launch(pipeline, NULL);
+
+ if (!mdecoder->pipe)
+ {
+ WLog_ERR(TAG, "Failed to create new pipe");
+ return FALSE;
+ }
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosource");
+ else
+ mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosource");
+
+ if (!mdecoder->src)
+ {
+ WLog_ERR(TAG, "Failed to get appsrc");
+ return FALSE;
+ }
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videoqueue");
+ else
+ mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audioqueue");
+
+ if (!mdecoder->queue)
+ {
+ WLog_ERR(TAG, "Failed to get queue");
+ return FALSE;
+ }
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosink");
+ else
+ mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosink");
+
+ if (!mdecoder->outsink)
+ {
+ WLog_ERR(TAG, "Failed to get sink");
+ return FALSE;
+ }
+
+ g_signal_connect(mdecoder->outsink, "child-added", G_CALLBACK(cb_child_added), mdecoder);
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_AUDIO)
+ {
+ mdecoder->volume = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiovolume");
+
+ if (!mdecoder->volume)
+ {
+ WLog_ERR(TAG, "Failed to get volume");
+ return FALSE;
+ }
+
+ tsmf_gstreamer_change_volume((ITSMFDecoder*)mdecoder, mdecoder->gstVolume * ((double)10000),
+ mdecoder->gstMuted);
+ }
+
+ tsmf_platform_register_handler(mdecoder);
+ /* AppSrc settings */
+ GstAppSrcCallbacks callbacks = {
+ tsmf_gstreamer_need_data, tsmf_gstreamer_enough_data, tsmf_gstreamer_seek_data, { NULL }
+ };
+ g_object_set(mdecoder->src, "format", GST_FORMAT_TIME, NULL);
+ g_object_set(mdecoder->src, "is-live", FALSE, NULL);
+ g_object_set(mdecoder->src, "block", FALSE, NULL);
+ g_object_set(mdecoder->src, "blocksize", 1024, NULL);
+ gst_app_src_set_caps((GstAppSrc*)mdecoder->src, mdecoder->gst_caps);
+ gst_app_src_set_callbacks((GstAppSrc*)mdecoder->src, &callbacks, mdecoder, NULL);
+ gst_app_src_set_stream_type((GstAppSrc*)mdecoder->src, GST_APP_STREAM_TYPE_SEEKABLE);
+ gst_app_src_set_latency((GstAppSrc*)mdecoder->src, 0, -1);
+ gst_app_src_set_max_bytes((GstAppSrc*)mdecoder->src, (guint64)0); // unlimited
+ g_object_set(G_OBJECT(mdecoder->queue), "use-buffering", FALSE, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "use-rate-estimate", FALSE, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "max-size-buffers", 0, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "max-size-bytes", 0, NULL);
+ g_object_set(G_OBJECT(mdecoder->queue), "max-size-time", (guint64)0, NULL);
+
+ /* Only set these properties if not an autosink, otherwise we will set properties when real
+ * sinks are added */
+ if (!g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoVideoSink") &&
+ !g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoAudioSink"))
+ {
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink,
+ 10000000); /* nanoseconds */
+ }
+ else
+ {
+ gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink,
+ 10000000); /* nanoseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "buffer-time", (gint64)20000,
+ NULL); /* microseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "drift-tolerance", (gint64)20000,
+ NULL); /* microseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "latency-time", (gint64)10000,
+ NULL); /* microseconds */
+ g_object_set(G_OBJECT(mdecoder->outsink), "slave-method", 1, NULL);
+ }
+ g_object_set(G_OBJECT(mdecoder->outsink), "sync", TRUE,
+ NULL); /* synchronize on the clock */
+ g_object_set(G_OBJECT(mdecoder->outsink), "async", TRUE, NULL); /* no async state changes */
+ }
+
+ tsmf_window_create(mdecoder);
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_READY);
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
+ mdecoder->pipeline_start_time_valid = 0;
+ mdecoder->shutdown = 0;
+ mdecoder->paused = FALSE;
+
+ GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(mdecoder->pipe), GST_DEBUG_GRAPH_SHOW_ALL,
+ get_type(mdecoder));
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_decodeEx(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size,
+ UINT32 extensions, UINT64 start_time, UINT64 end_time,
+ UINT64 duration)
+{
+ GstBuffer* gst_buf;
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ UINT64 sample_time = tsmf_gstreamer_timestamp_ms_to_gst(start_time);
+ BOOL useTimestamps = TRUE;
+
+ if (!mdecoder)
+ {
+ WLog_ERR(TAG, "Decoder not initialized!");
+ return FALSE;
+ }
+
+ /*
+ * This function is always called from a stream-specific thread.
+ * It should be alright to block here if necessary.
+ * We don't expect to block here often, since the pipeline should
+ * have more than enough buffering.
+ */
+ DEBUG_TSMF(
+ "%s. Start:(%" PRIu64 ") End:(%" PRIu64 ") Duration:(%" PRIu64 ") Last Start:(%" PRIu64 ")",
+ get_type(mdecoder), start_time, end_time, duration, mdecoder->last_sample_start_time);
+
+ if (mdecoder->shutdown)
+ {
+ WLog_ERR(TAG, "decodeEx called on shutdown decoder");
+ return TRUE;
+ }
+
+ if (mdecoder->gst_caps == NULL)
+ {
+ WLog_ERR(TAG, "tsmf_gstreamer_set_format not called or invalid format.");
+ return FALSE;
+ }
+
+ if (!mdecoder->pipe)
+ tsmf_gstreamer_pipeline_build(mdecoder);
+
+ if (!mdecoder->src)
+ {
+ WLog_ERR(
+ TAG,
+ "failed to construct pipeline correctly. Unable to push buffer to source element.");
+ return FALSE;
+ }
+
+ gst_buf = tsmf_get_buffer_from_data(data, data_size);
+
+ if (gst_buf == NULL)
+ {
+ WLog_ERR(TAG, "tsmf_get_buffer_from_data(%p, %" PRIu32 ") failed.", (void*)data, data_size);
+ return FALSE;
+ }
+
+ /* Relative timestamping will sometimes be set to 0
+ * so we ignore these timestamps just to be safe(bit 8)
+ */
+ if (extensions & 0x00000080)
+ {
+ DEBUG_TSMF("Ignoring the timestamps - relative - bit 8");
+ useTimestamps = FALSE;
+ }
+
+ /* If no timestamps exist then we dont want to look at the timestamp values (bit 7) */
+ if (extensions & 0x00000040)
+ {
+ DEBUG_TSMF("Ignoring the timestamps - none - bit 7");
+ useTimestamps = FALSE;
+ }
+
+ /* If performing a seek */
+ if (mdecoder->seeking)
+ {
+ mdecoder->seeking = FALSE;
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED);
+ mdecoder->pipeline_start_time_valid = 0;
+ }
+
+ if (mdecoder->pipeline_start_time_valid)
+ {
+ DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time);
+
+ /* Adjusted the condition for a seek to be based on start time only
+ * WMV1 and WMV2 files in particular have bad end time and duration values
+ * there seems to be no real side effects of just using the start time instead
+ */
+ UINT64 minTime = mdecoder->last_sample_start_time - (UINT64)SEEK_TOLERANCE;
+ UINT64 maxTime = mdecoder->last_sample_start_time + (UINT64)SEEK_TOLERANCE;
+
+ /* Make sure the minTime stops at 0 , should we be at the beginning of the stream */
+ if (mdecoder->last_sample_start_time < (UINT64)SEEK_TOLERANCE)
+ minTime = 0;
+
+ /* If the start_time is valid and different from the previous start time by more than the
+ * seek tolerance, then we have a seek condition */
+ if (((start_time > maxTime) || (start_time < minTime)) && useTimestamps)
+ {
+ DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64
+ "] > last_sample_start_time=[%" PRIu64 "] OR ",
+ start_time, mdecoder->last_sample_start_time);
+ DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64
+ "] < last_sample_start_time=[%" PRIu64 "] with",
+ start_time, mdecoder->last_sample_start_time);
+ DEBUG_TSMF(
+ "tsmf_gstreamer_decodeEX: a tolerance of more than [%lu] from the last sample",
+ SEEK_TOLERANCE);
+ DEBUG_TSMF("tsmf_gstreamer_decodeEX: minTime=[%" PRIu64 "] maxTime=[%" PRIu64 "]",
+ minTime, maxTime);
+
+ mdecoder->seeking = TRUE;
+
+ /* since we cant make the gstreamer pipeline jump to the new start time after a seek -
+ * we just maintain a offset between realtime and gstreamer time
+ */
+ mdecoder->seek_offset = start_time;
+ }
+ }
+ else
+ {
+ DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time);
+ /* Always set base/start time to 0. Will use seek offset to translate real buffer times
+ * back to 0. This allows the video to be started from anywhere and the ability to handle
+ * seeks without rebuilding the pipeline, etc. since that is costly
+ */
+ gst_element_set_base_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0));
+ gst_element_set_start_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0));
+ mdecoder->pipeline_start_time_valid = 1;
+
+ /* Set the seek offset if buffer has valid timestamps. */
+ if (useTimestamps)
+ mdecoder->seek_offset = start_time;
+
+ if (!gst_element_seek(mdecoder->pipe, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
+ GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
+ {
+ WLog_ERR(TAG, "seek failed");
+ }
+ }
+
+#if GST_VERSION_MAJOR > 0
+ if (useTimestamps)
+ GST_BUFFER_PTS(gst_buf) =
+ sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset);
+ else
+ GST_BUFFER_PTS(gst_buf) = GST_CLOCK_TIME_NONE;
+#else
+ if (useTimestamps)
+ GST_BUFFER_TIMESTAMP(gst_buf) =
+ sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset);
+ else
+ GST_BUFFER_TIMESTAMP(gst_buf) = GST_CLOCK_TIME_NONE;
+#endif
+ GST_BUFFER_DURATION(gst_buf) = GST_CLOCK_TIME_NONE;
+ GST_BUFFER_OFFSET(gst_buf) = GST_BUFFER_OFFSET_NONE;
+#if GST_VERSION_MAJOR > 0
+#else
+ gst_buffer_set_caps(gst_buf, mdecoder->gst_caps);
+#endif
+ gst_app_src_push_buffer(GST_APP_SRC(mdecoder->src), gst_buf);
+
+ /* Should only update the last timestamps if the current ones are valid */
+ if (useTimestamps)
+ {
+ mdecoder->last_sample_start_time = start_time;
+ mdecoder->last_sample_end_time = end_time;
+ }
+
+ if (mdecoder->pipe && (GST_STATE(mdecoder->pipe) != GST_STATE_PLAYING))
+ {
+ DEBUG_TSMF("%s: state=%s", get_type(mdecoder),
+ gst_element_state_get_name(GST_STATE(mdecoder->pipe)));
+
+ DEBUG_TSMF("%s Paused: %" PRIi32 " Shutdown: %i Ready: %" PRIi32 "", get_type(mdecoder),
+ mdecoder->paused, mdecoder->shutdown, mdecoder->ready);
+ if (!mdecoder->paused && !mdecoder->shutdown && mdecoder->ready)
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_control(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder)
+ {
+ WLog_ERR(TAG, "Control called with no decoder!");
+ return TRUE;
+ }
+
+ if (control_msg == Control_Pause)
+ {
+ DEBUG_TSMF("Control_Pause %s", get_type(mdecoder));
+
+ if (mdecoder->paused)
+ {
+ WLog_ERR(TAG, "%s: Ignoring Control_Pause, already received!", get_type(mdecoder));
+ return TRUE;
+ }
+
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED);
+ mdecoder->shutdown = 0;
+ mdecoder->paused = TRUE;
+ }
+ else if (control_msg == Control_Resume)
+ {
+ DEBUG_TSMF("Control_Resume %s", get_type(mdecoder));
+
+ if (!mdecoder->paused && !mdecoder->shutdown)
+ {
+ WLog_ERR(TAG, "%s: Ignoring Control_Resume, already received!", get_type(mdecoder));
+ return TRUE;
+ }
+
+ mdecoder->shutdown = 0;
+ mdecoder->paused = FALSE;
+ }
+ else if (control_msg == Control_Stop)
+ {
+ DEBUG_TSMF("Control_Stop %s", get_type(mdecoder));
+
+ if (mdecoder->shutdown)
+ {
+ WLog_ERR(TAG, "%s: Ignoring Control_Stop, already received!", get_type(mdecoder));
+ return TRUE;
+ }
+
+ /* Reset stamps, flush buffers, etc */
+ if (mdecoder->pipe)
+ {
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL);
+ tsmf_window_destroy(mdecoder);
+ tsmf_gstreamer_clean_up(mdecoder);
+ }
+ mdecoder->seek_offset = 0;
+ mdecoder->pipeline_start_time_valid = 0;
+ mdecoder->shutdown = 1;
+ }
+ else if (control_msg == Control_Restart)
+ {
+ DEBUG_TSMF("Control_Restart %s", get_type(mdecoder));
+ mdecoder->shutdown = 0;
+ mdecoder->paused = FALSE;
+
+ if (mdecoder->pipeline_start_time_valid)
+ tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING);
+ }
+ else
+ WLog_ERR(TAG, "Unknown control message %08x", control_msg);
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("");
+
+ if (!mdecoder)
+ return FALSE;
+
+ guint clbuff = 0;
+
+ if (G_IS_OBJECT(mdecoder->queue))
+ g_object_get(mdecoder->queue, "current-level-buffers", &clbuff, NULL);
+
+ DEBUG_TSMF("%s buffer level %u", get_type(mdecoder), clbuff);
+ return clbuff;
+}
+
+static void tsmf_gstreamer_free(ITSMFDecoder* decoder)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("%s", get_type(mdecoder));
+
+ if (mdecoder)
+ {
+ tsmf_window_destroy(mdecoder);
+ tsmf_gstreamer_clean_up(mdecoder);
+
+ if (mdecoder->gst_caps)
+ gst_caps_unref(mdecoder->gst_caps);
+
+ tsmf_platform_free(mdecoder);
+ ZeroMemory(mdecoder, sizeof(TSMFGstreamerDecoder));
+ free(mdecoder);
+ mdecoder = NULL;
+ }
+}
+
+static UINT64 tsmf_gstreamer_get_running_time(ITSMFDecoder* decoder)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+
+ if (!mdecoder)
+ return 0;
+
+ if (!mdecoder->outsink)
+ return mdecoder->last_sample_start_time;
+
+ if (!mdecoder->pipe)
+ return 0;
+
+ GstFormat fmt = GST_FORMAT_TIME;
+ gint64 pos = 0;
+#if GST_VERSION_MAJOR > 0
+ gst_element_query_position(mdecoder->pipe, fmt, &pos);
+#else
+ gst_element_query_position(mdecoder->pipe, &fmt, &pos);
+#endif
+ return (UINT64)(pos / 100 + mdecoder->seek_offset);
+}
+
+static BOOL tsmf_gstreamer_update_rendering_area(ITSMFDecoder* decoder, int newX, int newY,
+ int newWidth, int newHeight, int numRectangles,
+ RDP_RECT* rectangles)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("x=%d, y=%d, w=%d, h=%d, rect=%d", newX, newY, newWidth, newHeight, numRectangles);
+
+ if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO)
+ {
+ return tsmf_window_resize(mdecoder, newX, newY, newWidth, newHeight, numRectangles,
+ rectangles) == 0;
+ }
+
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_ack(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("");
+ mdecoder->ack_cb = NULL;
+ mdecoder->stream = stream;
+ return TRUE;
+}
+
+static BOOL tsmf_gstreamer_sync(ITSMFDecoder* decoder, void (*cb)(void*), void* stream)
+{
+ TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder;
+ DEBUG_TSMF("");
+ mdecoder->sync_cb = NULL;
+ mdecoder->stream = stream;
+ return TRUE;
+}
+
+FREERDP_ENTRY_POINT(ITSMFDecoder* gstreamer_freerdp_tsmf_client_decoder_subsystem_entry(void))
+{
+ TSMFGstreamerDecoder* decoder;
+
+#if GST_CHECK_VERSION(0, 10, 31)
+ if (!gst_is_initialized())
+ {
+ gst_init(NULL, NULL);
+ }
+#else
+ gst_init(NULL, NULL);
+#endif
+
+ decoder = calloc(1, sizeof(TSMFGstreamerDecoder));
+
+ if (!decoder)
+ return NULL;
+
+ decoder->iface.SetFormat = tsmf_gstreamer_set_format;
+ decoder->iface.Decode = NULL;
+ decoder->iface.GetDecodedData = NULL;
+ decoder->iface.GetDecodedFormat = NULL;
+ decoder->iface.GetDecodedDimension = NULL;
+ decoder->iface.GetRunningTime = tsmf_gstreamer_get_running_time;
+ decoder->iface.UpdateRenderingArea = tsmf_gstreamer_update_rendering_area;
+ decoder->iface.Free = tsmf_gstreamer_free;
+ decoder->iface.Control = tsmf_gstreamer_control;
+ decoder->iface.DecodeEx = tsmf_gstreamer_decodeEx;
+ decoder->iface.ChangeVolume = tsmf_gstreamer_change_volume;
+ decoder->iface.BufferLevel = tsmf_gstreamer_buffer_level;
+ decoder->iface.SetAckFunc = tsmf_gstreamer_ack;
+ decoder->iface.SetSyncFunc = tsmf_gstreamer_sync;
+ decoder->paused = FALSE;
+ decoder->gstVolume = 0.5;
+ decoder->gstMuted = FALSE;
+ decoder->state = GST_STATE_VOID_PENDING; /* No real state yet */
+ decoder->last_sample_start_time = 0;
+ decoder->last_sample_end_time = 0;
+ decoder->seek_offset = 0;
+ decoder->seeking = FALSE;
+
+ if (tsmf_platform_create(decoder) < 0)
+ {
+ free(decoder);
+ return NULL;
+ }
+
+ return (ITSMFDecoder*)decoder;
+}
diff --git a/channels/tsmf/client/gstreamer/tsmf_platform.h b/channels/tsmf/client/gstreamer/tsmf_platform.h
new file mode 100644
index 0000000..681095c
--- /dev/null
+++ b/channels/tsmf/client/gstreamer/tsmf_platform.h
@@ -0,0 +1,85 @@
+/*
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Redirection Virtual Channel - GStreamer Decoder
+ * platform specific functions
+ *
+ * (C) Copyright 2014 Thincast Technologies GmbH
+ * (C) Copyright 2014 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H
+#define FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H
+
+#include <gst/gst.h>
+#include <tsmf_decoder.h>
+
+typedef struct
+{
+ ITSMFDecoder iface;
+
+ int media_type; /* TSMF_MAJOR_TYPE_AUDIO or TSMF_MAJOR_TYPE_VIDEO */
+
+ gint64 duration;
+
+ GstState state;
+ GstCaps* gst_caps;
+
+ GstElement* pipe;
+ GstElement* src;
+ GstElement* queue;
+ GstElement* outsink;
+ GstElement* volume;
+
+ BOOL ready;
+ BOOL paused;
+ UINT64 last_sample_start_time;
+ UINT64 last_sample_end_time;
+ BOOL seeking;
+ UINT64 seek_offset;
+
+ double gstVolume;
+ BOOL gstMuted;
+
+ int pipeline_start_time_valid; /* We've set the start time and have not reset the pipeline */
+ int shutdown; /* The decoder stream is shutting down */
+
+ void* platform;
+
+ BOOL (*ack_cb)(void*, BOOL);
+ void (*sync_cb)(void*);
+ void* stream;
+
+} TSMFGstreamerDecoder;
+
+const char* tsmf_platform_get_video_sink(void);
+const char* tsmf_platform_get_audio_sink(void);
+
+int tsmf_platform_create(TSMFGstreamerDecoder* decoder);
+int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder);
+int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder);
+int tsmf_platform_free(TSMFGstreamerDecoder* decoder);
+
+int tsmf_window_create(TSMFGstreamerDecoder* decoder);
+int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height,
+ int nr_rect, RDP_RECT* visible);
+int tsmf_window_destroy(TSMFGstreamerDecoder* decoder);
+
+int tsmf_window_map(TSMFGstreamerDecoder* decoder);
+int tsmf_window_unmap(TSMFGstreamerDecoder* decoder);
+
+BOOL tsmf_gstreamer_add_pad(TSMFGstreamerDecoder* mdecoder);
+void tsmf_gstreamer_remove_pad(TSMFGstreamerDecoder* mdecoder);
+
+#endif /* FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H */