From 4aad84e8446f34dec2dd206ce9586acb7d031874 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Wed, 25 Nov 2015 17:24:27 +0900 Subject: [PATCH 01/16] Apply Wifi Display feature Change-Id: I5c928adb9525f9c69bafcd263d0d538490ce42c1 --- .gitignore | 68 - .gitmodules | 3 - gst-rtsp-server.manifest | 5 + gst/rtsp-server/Makefile.am | 10 +- gst/rtsp-server/gstwfdmessage.c | 1968 ++++++++++++++++++++ gst/rtsp-server/gstwfdmessage.h | 618 +++++++ gst/rtsp-server/rtsp-client-wfd.c | 2959 ++++++++++++++++++++++++++++++ gst/rtsp-server/rtsp-client-wfd.h | 221 +++ gst/rtsp-server/rtsp-client.c | 36 +- gst/rtsp-server/rtsp-client.h | 5 +- gst/rtsp-server/rtsp-media-factory-wfd.c | 1200 ++++++++++++ gst/rtsp-server/rtsp-media-factory-wfd.h | 131 ++ gst/rtsp-server/rtsp-server-wfd.c | 332 ++++ gst/rtsp-server/rtsp-server-wfd.h | 155 ++ gst/rtsp-server/rtsp-stream.c | 23 + gst/rtsp-server/rtsp-stream.h | 1 + packaging/common.tar.bz2 | Bin 0 -> 102577 bytes packaging/gst-rtsp-server.spec | 76 + 18 files changed, 7732 insertions(+), 79 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitmodules create mode 100644 gst-rtsp-server.manifest create mode 100755 gst/rtsp-server/gstwfdmessage.c create mode 100755 gst/rtsp-server/gstwfdmessage.h create mode 100644 gst/rtsp-server/rtsp-client-wfd.c create mode 100644 gst/rtsp-server/rtsp-client-wfd.h create mode 100644 gst/rtsp-server/rtsp-media-factory-wfd.c create mode 100644 gst/rtsp-server/rtsp-media-factory-wfd.h create mode 100644 gst/rtsp-server/rtsp-server-wfd.c create mode 100644 gst/rtsp-server/rtsp-server-wfd.h create mode 100644 packaging/common.tar.bz2 create mode 100644 packaging/gst-rtsp-server.spec diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 574f665..0000000 --- a/.gitignore +++ /dev/null @@ -1,68 +0,0 @@ -*.[oa] -*.pyc -*.gcda -*.gcno -*.la -*.lo -*.loT -*.sw[po] -*.tar.* -*~ -*.gc?? -.deps -.libs -ABOUT-NLS -INSTALL -Makefile -Makefile.in -aclocal.m4 -autom4te.cache -autoregen.sh -compile -config.guess -config.h -config.h.in -config.log -config.rpath -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing -stamp-h1 -tags -gst-rtsp.spec -stamp-h.in -.dirstamp - -/m4/*m4 - -/gst/rtsp-server/GstRtspServer-1.0.gir -/gst/rtsp-server/GstRtspServer-1.0.typelib - -/examples/test-multicast -/examples/test-multicast2 - -/test-driver -/tests/check/gst/*.log -/tests/check/gst/*.trs -/tests/check/test-suite.log -/tests/check/gst/addresspool -/tests/check/gst/client -/tests/check/gst/media -/tests/check/gst/mediafactory -/tests/check/gst/mountpoints -/tests/check/gst/permissions -/tests/check/gst/rtspserver -/tests/check/gst/sessionmedia -/tests/check/gst/sessionpool -/tests/check/gst/stream -/tests/check/gst/threadpool -/tests/check/gst/token -/tests/check/test-registry.reg -/tests/test-reuse - -/po diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index a6b1eda..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "common"] - path = common - url = git://anongit.freedesktop.org/gstreamer/common diff --git a/gst-rtsp-server.manifest b/gst-rtsp-server.manifest new file mode 100644 index 0000000..a76fdba --- /dev/null +++ b/gst-rtsp-server.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/gst/rtsp-server/Makefile.am b/gst/rtsp-server/Makefile.am index 4fcd366..60eaf3e 100644 --- a/gst/rtsp-server/Makefile.am +++ b/gst/rtsp-server/Makefile.am @@ -7,6 +7,7 @@ public_headers = \ rtsp-thread-pool.h \ rtsp-media.h \ rtsp-media-factory.h \ + rtsp-media-factory-wfd.h \ rtsp-media-factory-uri.h \ rtsp-mount-points.h \ rtsp-permissions.h \ @@ -16,8 +17,11 @@ public_headers = \ rtsp-session-media.h \ rtsp-session-pool.h \ rtsp-token.h \ + rtsp-client-wfd.h \ rtsp-client.h \ - rtsp-server.h + rtsp-server-wfd.h \ + rtsp-server.h \ + gstwfdmessage.h c_sources = \ rtsp-auth.c \ @@ -28,6 +32,7 @@ c_sources = \ rtsp-thread-pool.c \ rtsp-media.c \ rtsp-media-factory.c \ + rtsp-media-factory-wfd.c \ rtsp-media-factory-uri.c \ rtsp-mount-points.c \ rtsp-permissions.c \ @@ -37,7 +42,10 @@ c_sources = \ rtsp-session-media.c \ rtsp-session-pool.c \ rtsp-token.c \ + gstwfdmessage.c \ + rtsp-client-wfd.c \ rtsp-client.c \ + rtsp-server-wfd.c \ rtsp-server.c noinst_HEADERS = diff --git a/gst/rtsp-server/gstwfdmessage.c b/gst/rtsp-server/gstwfdmessage.c new file mode 100755 index 0000000..b70e57d --- /dev/null +++ b/gst/rtsp-server/gstwfdmessage.c @@ -0,0 +1,1968 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * SECTION:gstwfdmessage + * @short_description: Helper methods for dealing with WFD messages + * + * + * + * The GstWFDMessage helper functions makes it easy to parse and create WFD + * messages. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "gstwfdmessage.h" + +#define EDID_BLOCK_SIZE 128 +#define EDID_BLOCK_COUNT_MAX_SIZE 256 +#define MAX_PORT_SIZE 65535 + +#define FREE_STRING(field) g_free (field); (field) = NULL +#define REPLACE_STRING(field, val) FREE_STRING(field); (field) = g_strdup (val) + +#define INIT_ARRAY(field, type, init_func) \ +G_STMT_START { \ + if (field) { \ + guint i; \ + for(i = 0; i < (field)->len; i++) \ + init_func (&g_array_index ((field), type, i)); \ + g_array_set_size ((field), 0); \ + } \ + else \ + (field) = g_array_new (FALSE, TRUE, sizeof (type)); \ +} G_STMT_END + +#define FREE_ARRAY(field) \ +G_STMT_START { \ + if (field) \ + g_array_free ((field), TRUE); \ + (field) = NULL; \ +} G_STMT_END + +#define DEFINE_STRING_SETTER(field) \ +GstWFDResult gst_wfd_message_set_##field (GstWFDMessage *msg, const gchar *val) { \ + g_free (msg->field); \ + msg->field = g_strdup (val); \ + return GST_WFD_OK; \ +} +#define DEFINE_STRING_GETTER(field) \ +const gchar* gst_wfd_message_get_##field (const GstWFDMessage *msg) { \ + return msg->field; \ +} + +#define DEFINE_ARRAY_LEN(field) \ +guint gst_wfd_message_##field##_len (const GstWFDMessage *msg) { \ + return msg->field->len; \ +} +#define DEFINE_ARRAY_GETTER(method, field, type) \ +const type * gst_wfd_message_get_##method (const GstWFDMessage *msg, guint idx) { \ + return &g_array_index (msg->field, type, idx); \ +} +#define DEFINE_PTR_ARRAY_GETTER(method, field, type) \ +const type gst_wfd_message_get_##method (const GstWFDMessage *msg, guint idx) { \ + return g_array_index (msg->field, type, idx); \ +} +#define DEFINE_ARRAY_INSERT(method, field, intype, dup_method, type) \ +GstWFDResult gst_wfd_message_insert_##method (GstWFDMessage *msg, gint idx, intype val) { \ + type vt; \ + type* v = &vt; \ + dup_method (v, val); \ + if (idx == -1) \ + g_array_append_val (msg->field, vt); \ + else \ + g_array_insert_val (msg->field, idx, vt); \ + return GST_WFD_OK; \ +} + +#define DEFINE_ARRAY_REPLACE(method, field, intype, free_method, dup_method, type) \ +GstWFDResult gst_wfd_message_replace_##method (GstWFDMessage *msg, guint idx, intype val) { \ + type *v = &g_array_index (msg->field, type, idx); \ + free_method (v); \ + dup_method (v, val); \ + return GST_WFD_OK; \ +} +#define DEFINE_ARRAY_REMOVE(method, field, type, free_method) \ +GstWFDResult gst_wfd_message_remove_##method (GstWFDMessage *msg, guint idx) { \ + type *v = &g_array_index (msg->field, type, idx); \ + free_method (v); \ + g_array_remove_index (msg->field, idx); \ + return GST_WFD_OK; \ +} +#define DEFINE_ARRAY_ADDER(method, type) \ +GstWFDResult gst_wfd_message_add_##method (GstWFDMessage *msg, const type val) { \ + return gst_wfd_message_insert_##method (msg, -1, val); \ +} + +#define dup_string(v,val) ((*v) = g_strdup (val)) +#define INIT_STR_ARRAY(field) \ + INIT_ARRAY (field, gchar *, free_string) +#define DEFINE_STR_ARRAY_GETTER(method, field) \ + DEFINE_PTR_ARRAY_GETTER(method, field, gchar *) +#define DEFINE_STR_ARRAY_INSERT(method, field) \ + DEFINE_ARRAY_INSERT (method, field, const gchar *, dup_string, gchar *) +#define DEFINE_STR_ARRAY_ADDER(method, field) \ + DEFINE_ARRAY_ADDER (method, gchar *) +#define DEFINE_STR_ARRAY_REPLACE(method, field) \ + DEFINE_ARRAY_REPLACE (method, field, const gchar *, free_string, dup_string, gchar *) +#define DEFINE_STR_ARRAY_REMOVE(method, field) \ + DEFINE_ARRAY_REMOVE (method, field, gchar *, free_string) + +static GstWFDMessage *gst_wfd_message_boxed_copy (GstWFDMessage * orig); +static void gst_wfd_message_boxed_free (GstWFDMessage * msg); + +G_DEFINE_BOXED_TYPE (GstWFDMessage, gst_wfd_message, gst_wfd_message_boxed_copy, + gst_wfd_message_boxed_free); + +static GstWFDMessage * +gst_wfd_message_boxed_copy (GstWFDMessage * orig) +{ + GstWFDMessage *copy; + + if (gst_wfd_message_copy (orig, ©) == GST_WFD_OK) + return copy; + + return NULL; +} + +static void +gst_wfd_message_boxed_free (GstWFDMessage * msg) +{ + gst_wfd_message_free (msg); +} + +/** + * gst_wfd_message_new: + * @msg: (out) (transfer full): pointer to new #GstWFDMessage + * + * Allocate a new GstWFDMessage and store the result in @msg. + * + * Returns: a #GstWFDResult. + */ +GstWFDResult +gst_wfd_message_new (GstWFDMessage ** msg) +{ + GstWFDMessage *newmsg; + + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + newmsg = g_new0 (GstWFDMessage, 1); + + *msg = newmsg; + + return gst_wfd_message_init (newmsg); +} + +/** + * gst_wfd_message_init: + * @msg: a #GstWFDMessage + * + * Initialize @msg so that its contents are as if it was freshly allocated + * with gst_wfd_message_new(). This function is mostly used to initialize a message + * allocated on the stack. gst_wfd_message_uninit() undoes this operation. + * + * When this function is invoked on newly allocated data (with malloc or on the + * stack), its contents should be set to 0 before calling this function. + * + * Returns: a #GstWFDResult. + */ +GstWFDResult +gst_wfd_message_init (GstWFDMessage * msg) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + return GST_WFD_OK; +} + +/** + * gst_wfd_message_uninit: + * @msg: a #GstWFDMessage + * + * Free all resources allocated in @msg. @msg should not be used anymore after + * this function. This function should be used when @msg was allocated on the + * stack and initialized with gst_wfd_message_init(). + * + * Returns: a #GstWFDResult. + */ +GstWFDResult +gst_wfd_message_uninit (GstWFDMessage * msg) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (msg->audio_codecs) { + guint i = 0; + if (msg->audio_codecs->list) { + for (; i < msg->audio_codecs->count; i++) { + FREE_STRING(msg->audio_codecs->list[i].audio_format); + msg->audio_codecs->list[i].modes = 0; + msg->audio_codecs->list[i].latency = 0; + } + FREE_STRING(msg->audio_codecs->list); + } + FREE_STRING(msg->audio_codecs); + } + + if (msg->video_formats) { + FREE_STRING(msg->video_formats->list); + FREE_STRING(msg->video_formats); + } + + if (msg->video_3d_formats) { + FREE_STRING(msg->video_3d_formats->list); + FREE_STRING(msg->video_3d_formats); + } + + if (msg->content_protection) { + if (msg->content_protection->hdcp2_spec) { + FREE_STRING(msg->content_protection->hdcp2_spec->hdcpversion); + FREE_STRING(msg->content_protection->hdcp2_spec->TCPPort); + FREE_STRING(msg->content_protection->hdcp2_spec); + } + FREE_STRING(msg->content_protection); + } + + if (msg->display_edid) { + if (msg->display_edid->edid_payload) + FREE_STRING(msg->display_edid->edid_payload); + FREE_STRING(msg->display_edid); + } + + if (msg->coupled_sink) { + if (msg->coupled_sink->coupled_sink_cap) { + FREE_STRING(msg->coupled_sink->coupled_sink_cap->sink_address); + FREE_STRING(msg->coupled_sink->coupled_sink_cap); + } + FREE_STRING(msg->coupled_sink); + } + + if (msg->trigger_method) { + FREE_STRING(msg->trigger_method->wfd_trigger_method); + FREE_STRING(msg->trigger_method); + } + + if (msg->presentation_url) { + FREE_STRING(msg->presentation_url->wfd_url0); + FREE_STRING(msg->presentation_url->wfd_url1); + FREE_STRING(msg->presentation_url); + } + + if (msg->client_rtp_ports) { + FREE_STRING(msg->client_rtp_ports->profile); + FREE_STRING(msg->client_rtp_ports->mode); + FREE_STRING(msg->client_rtp_ports); + } + + if (msg->route) { + FREE_STRING(msg->route->destination); + FREE_STRING(msg->route); + } + + if (msg->I2C) { + FREE_STRING(msg->I2C); + } + + if (msg->av_format_change_timing) { + FREE_STRING(msg->av_format_change_timing); + } + + if (msg->preferred_display_mode) { + FREE_STRING(msg->preferred_display_mode); + } + + if (msg->standby_resume_capability) { + FREE_STRING(msg->standby_resume_capability); + } + + if (msg->standby) { + FREE_STRING(msg->standby); + } + + if (msg->connector_type) { + FREE_STRING(msg->connector_type); + } + + if (msg->idr_request) { + FREE_STRING(msg->idr_request); + } + + return GST_WFD_OK; +} + +/** + * gst_wfd_message_copy: + * @msg: a #GstWFDMessage + * @copy: (out) (transfer full): pointer to new #GstWFDMessage + * + * Allocate a new copy of @msg and store the result in @copy. The value in + * @copy should be release with gst_wfd_message_free function. + * + * Returns: a #GstWFDResult + * + * Since: 1.6 + */ +GstWFDResult +gst_wfd_message_copy (const GstWFDMessage * msg, GstWFDMessage ** copy) +{ + GstWFDResult ret; + GstWFDMessage *cp; + + if (msg == NULL) + return GST_WFD_EINVAL; + + ret = gst_wfd_message_new (copy); + if (ret != GST_WFD_OK) + return ret; + + cp = *copy; + + /* TODO-WFD */ + if (msg->client_rtp_ports) { + cp->client_rtp_ports = g_malloc (sizeof (GstWFDClientRtpPorts)); + if (cp->client_rtp_ports) { + cp->client_rtp_ports->profile = g_strdup (msg->client_rtp_ports->profile); + cp->client_rtp_ports->rtp_port0 = msg->client_rtp_ports->rtp_port0; + cp->client_rtp_ports->rtp_port1 = msg->client_rtp_ports->rtp_port1; + cp->client_rtp_ports->mode = g_strdup (msg->client_rtp_ports->mode); + } + } + + return GST_WFD_OK; +} + + +static void +_read_string_space_ended (gchar * dest, guint size, gchar * src) +{ + guint idx = 0; + + while (!g_ascii_isspace (*src) && *src != '\0') { + if (idx < size - 1) + dest[idx++] = *src; + src++; + } + + if (size > 0) + dest[idx] = '\0'; + + return; +} + +static void +_read_string_attr_and_value (gchar * attr, gchar * value, guint tsize, + guint vsize, gchar del, gchar * src) +{ + guint idx; + + idx = 0; + + while (*src != del && *src != '\0') { + if (idx < tsize - 1) + attr[idx++] = *src; + src++; + } + + if (tsize > 0) + attr[idx] = '\0'; + + src++; + idx = 0; + + while (*src != '\0') { + if (idx < vsize - 1) + value[idx++] = *src; + src++; + } + + if (vsize > 0) + value[idx] = '\0'; + + return; +} + +static void +gst_wfd_parse_attribute (gchar * buffer, GstWFDMessage * msg) +{ + gchar attr[8192] = { 0 }; + gchar value[8192] = { 0 }; + gchar temp[8192] = { 0 }; + gchar *p = buffer; + gchar *v = value; + +#define WFD_SKIP_SPACE(q) if (*q && g_ascii_isspace (*q)) q++ +#define WFD_SKIP_EQUAL(q) if (*q && *q == '=') q++ +#define WFD_SKIP_COMMA(q) if (*q && g_ascii_ispunct (*q)) q++ +#define WFD_READ_STRING(field) _read_string_space_ended (temp, sizeof (temp), v); v+=strlen(temp); REPLACE_STRING (field, temp) +#define WFD_READ_UINT32(field) _read_string_space_ended (temp, sizeof (temp), v); v+=strlen(temp); field = strtoul (temp, NULL, 16) +#define WFD_READ_UINT32_DIGIT(field) _read_string_space_ended (temp, sizeof (temp), v); v+=strlen(temp); field = strtoul (temp, NULL, 10) + + _read_string_attr_and_value (attr, value, sizeof (attr), sizeof (value), ':', + p); + + if (!g_strcmp0 (attr, GST_STRING_WFD_AUDIO_CODECS)) { + msg->audio_codecs = g_new0 (GstWFDAudioCodeclist, 1); + if (strlen (v)) { + guint i = 0; + msg->audio_codecs->count = strlen (v) / 16; + msg->audio_codecs->list = + g_new0 (GstWFDAudioCodec, msg->audio_codecs->count); + for (; i < msg->audio_codecs->count; i++) { + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->audio_codecs->list[i].audio_format); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->audio_codecs->list[i].modes); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->audio_codecs->list[i].latency); + WFD_SKIP_COMMA (v); + } + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_VIDEO_FORMATS)) { + msg->video_formats = g_new0 (GstWFDVideoCodeclist, 1); + if (strlen (v)) { + msg->video_formats->count = 1; + msg->video_formats->list = g_new0 (GstWFDVideoCodec, 1); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->native); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats-> + list->preferred_display_mode_supported); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec.profile); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec.level); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.CEA_Support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.VESA_Support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.HH_Support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.latency); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.min_slice_size); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.slice_enc_params); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec. + misc_params.frame_rate_control_support); + WFD_SKIP_SPACE (v); + if (msg->video_formats->list->preferred_display_mode_supported == 1) { + WFD_READ_UINT32 (msg->video_formats->list->H264_codec.max_hres); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec.max_vres); + WFD_SKIP_SPACE (v); + } + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_3D_VIDEO_FORMATS)) { + msg->video_3d_formats = g_new0 (GstWFD3DFormats, 1); + if (strlen (v)) { + msg->video_3d_formats->count = 1; + msg->video_3d_formats->list = g_new0 (GstWFD3dCapList, 1); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->native); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats-> + list->preferred_display_mode_supported); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec.profile); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec.level); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec. + misc_params.video_3d_capability); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec. + misc_params.latency); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec. + misc_params.min_slice_size); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec. + misc_params.slice_enc_params); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_3d_formats->list->H264_codec. + misc_params.frame_rate_control_support); + WFD_SKIP_SPACE (v); + if (msg->video_formats->list->preferred_display_mode_supported == 1) { + WFD_READ_UINT32 (msg->video_formats->list->H264_codec.max_hres); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->video_formats->list->H264_codec.max_vres); + WFD_SKIP_SPACE (v); + } + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_CONTENT_PROTECTION)) { + msg->content_protection = g_new0 (GstWFDContentProtection, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + msg->content_protection->hdcp2_spec = g_new0 (GstWFDHdcp2Spec, 1); + if (strstr (v, "none")) { + msg->content_protection->hdcp2_spec->hdcpversion = g_strdup ("none"); + } else { + WFD_READ_STRING (msg->content_protection->hdcp2_spec->hdcpversion); + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->content_protection->hdcp2_spec->TCPPort); + } + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_DISPLAY_EDID)) { + msg->display_edid = g_new0 (GstWFDDisplayEdid, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + if (strstr (v, "none")) { + msg->display_edid->edid_supported = 0; + } else { + msg->display_edid->edid_supported = 1; + WFD_READ_UINT32 (msg->display_edid->edid_block_count); + WFD_SKIP_SPACE (v); + if (msg->display_edid->edid_block_count) { + gchar *edid_string = v; + int i = 0, j = 0; + guint32 payload_size = + EDID_BLOCK_SIZE * msg->display_edid->edid_block_count; + msg->display_edid->edid_payload = g_malloc (payload_size); + for (; + i < (EDID_BLOCK_SIZE * msg->display_edid->edid_block_count * 2); + j++) { + int k = 0, kk = 0; + if (edid_string[i] > 0x29 && edid_string[i] < 0x40) + k = edid_string[i] - 48; + else if (edid_string[i] > 0x60 && edid_string[i] < 0x67) + k = edid_string[i] - 87; + else if (edid_string[i] > 0x40 && edid_string[i] < 0x47) + k = edid_string[i] - 55; + + if (edid_string[i + 1] > 0x29 && edid_string[i + 1] < 0x40) + kk = edid_string[i + 1] - 48; + else if (edid_string[i + 1] > 0x60 && edid_string[i + 1] < 0x67) + kk = edid_string[i + 1] - 87; + else if (edid_string[i + 1] > 0x40 && edid_string[i + 1] < 0x47) + kk = edid_string[i + 1] - 55; + + msg->display_edid->edid_payload[j] = (k << 4) | kk; + i += 2; + } + //memcpy(msg->display_edid->edid_payload, v, payload_size); + v += (payload_size * 2); + } else + v += strlen (v); + } + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_COUPLED_SINK)) { + msg->coupled_sink = g_new0 (GstWFDCoupledSink, 1); + if (strlen (v)) { + msg->coupled_sink->coupled_sink_cap = g_new0 (GstWFDCoupled_sink_cap, 1); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->coupled_sink->coupled_sink_cap->status); + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->coupled_sink->coupled_sink_cap->sink_address); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_TRIGGER_METHOD)) { + msg->trigger_method = g_new0 (GstWFDTriggerMethod, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->trigger_method->wfd_trigger_method); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_PRESENTATION_URL)) { + msg->presentation_url = g_new0 (GstWFDPresentationUrl, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->presentation_url->wfd_url0); + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->presentation_url->wfd_url1); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_CLIENT_RTP_PORTS)) { + msg->client_rtp_ports = g_new0 (GstWFDClientRtpPorts, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->client_rtp_ports->profile); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32_DIGIT (msg->client_rtp_ports->rtp_port0); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32_DIGIT (msg->client_rtp_ports->rtp_port1); + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->client_rtp_ports->mode); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_ROUTE)) { + msg->route = g_new0 (GstWFDRoute, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + WFD_READ_STRING (msg->route->destination); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_I2C)) { + msg->I2C = g_new0 (GstWFDI2C, 1); + if (strlen (v)) { + msg->I2C->I2CPresent = TRUE; + WFD_SKIP_SPACE (v); + WFD_READ_UINT32_DIGIT (msg->I2C->I2C_port); + if (msg->I2C->I2C_port) + msg->I2C->I2CPresent = TRUE; + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_AV_FORMAT_CHANGE_TIMING)) { + msg->av_format_change_timing = g_new0 (GstWFDAVFormatChangeTiming, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->av_format_change_timing->PTS); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->av_format_change_timing->DTS); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_PREFERRED_DISPLAY_MODE)) { + msg->preferred_display_mode = g_new0 (GstWFDPreferredDisplayMode, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + if (!strstr (v, "none")) { + msg->preferred_display_mode->displaymodesupported = FALSE; + } else { + WFD_READ_UINT32 (msg->preferred_display_mode->p_clock); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->HB); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->HSPOL_HSOFF); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->HSW); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->V); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->VB); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->VSPOL_VSOFF); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->VSW); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->VBS3D); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->V2d_s3d_modes); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->P_depth); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec.profile); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec.level); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.CEA_Support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.VESA_Support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.HH_Support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.latency); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.min_slice_size); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.slice_enc_params); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec. + misc_params.frame_rate_control_support); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec.max_hres); + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->preferred_display_mode->H264_codec.max_vres); + WFD_SKIP_SPACE (v); + } + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_STANDBY_RESUME_CAPABILITY)) { + msg->standby_resume_capability = g_new0 (GstWFDStandbyResumeCapability, 1); + if (strlen (v)) { + WFD_SKIP_SPACE (v); + if (!g_strcmp0 (v, "supported")) + msg->standby_resume_capability->standby_resume_cap = TRUE; + else + msg->standby_resume_capability->standby_resume_cap = FALSE; + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_STANDBY)) { + msg->standby = g_new0 (GstWFDStandby, 1); + msg->standby->wfd_standby = TRUE; + } else if (!g_strcmp0 (attr, GST_STRING_WFD_CONNECTOR_TYPE)) { + msg->connector_type = g_new0 (GstWFDConnectorType, 1); + if (strlen (v)) { + msg->connector_type->supported = TRUE; + WFD_SKIP_SPACE (v); + WFD_READ_UINT32 (msg->connector_type->connector_type); + } + } else if (!g_strcmp0 (attr, GST_STRING_WFD_IDR_REQUEST)) { + msg->idr_request = g_new0 (GstWFDIdrRequest, 1); + msg->idr_request->idr_request = TRUE; + } + return; +} + +/** + * gst_wfd_message_parse_buffer: + * @data: the start of the buffer + * @size: the size of the buffer + * @msg: the result #GstSDPMessage + * + * Parse the contents of @size bytes pointed to by @data and store the result in + * @msg. + * + * Returns: #GST_SDP_OK on success. + */ +GstWFDResult +gst_wfd_message_parse_buffer (const guint8 * data, guint size, + GstWFDMessage * msg) +{ + gchar *p; + gchar buffer[255] = { 0 }; + guint idx = 0; + + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (data != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (size != 0, GST_WFD_EINVAL); + + p = (gchar *) data; + while (TRUE) { + + if (*p == '\0') + break; + + idx = 0; + while (*p != '\n' && *p != '\r' && *p != '\0') { + if (idx < sizeof (buffer) - 1) + buffer[idx++] = *p; + p++; + } + buffer[idx] = '\0'; + gst_wfd_parse_attribute (buffer, msg); + + if (*p == '\0') + break; + p += 2; + } + return GST_WFD_OK; +} + +/** + * gst_wfd_message_free: + * @msg: a #GstWFDMessage + * + * Free all resources allocated by @msg. @msg should not be used anymore after + * this function. This function should be used when @msg was dynamically + * allocated with gst_wfd_message_new(). + * + * Returns: a #GstWFDResult. + */ +GstWFDResult +gst_wfd_message_free (GstWFDMessage * msg) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + gst_wfd_message_uninit (msg); + g_free (msg); + + return GST_WFD_OK; +} + +/** + * gst_wfd_message_as_text: + * @msg: a #GstWFDMessage + * + * Convert the contents of @msg to a text string. + * + * Returns: A dynamically allocated string representing the WFD description. + */ +gchar * +gst_wfd_message_as_text (const GstWFDMessage * msg) +{ + /* change all vars so they match rfc? */ + GString *lines; + guint i; + + g_return_val_if_fail (msg != NULL, NULL); + + lines = g_string_new (""); + + /* list of audio codecs */ + if (msg->audio_codecs) { + g_string_append_printf (lines, GST_STRING_WFD_AUDIO_CODECS); + if (msg->audio_codecs->list) { + g_string_append_printf (lines, ":"); + for (i = 0; i < msg->audio_codecs->count; i++) { + g_string_append_printf (lines, " %s", + msg->audio_codecs->list[i].audio_format); + g_string_append_printf (lines, " %08x", + msg->audio_codecs->list[i].modes); + g_string_append_printf (lines, " %02x", + msg->audio_codecs->list[i].latency); + if ((i + 1) < msg->audio_codecs->count) + g_string_append_printf (lines, ","); + } + } + g_string_append_printf (lines, "\r\n"); + } + + /* list of video codecs */ + if (msg->video_formats) { + g_string_append_printf (lines, GST_STRING_WFD_VIDEO_FORMATS); + if (msg->video_formats->list) { + g_string_append_printf (lines, ":"); + g_string_append_printf (lines, " %02x", msg->video_formats->list->native); + g_string_append_printf (lines, " %02x", + msg->video_formats->list->preferred_display_mode_supported); + g_string_append_printf (lines, " %02x", + msg->video_formats->list->H264_codec.profile); + g_string_append_printf (lines, " %02x", + msg->video_formats->list->H264_codec.level); + g_string_append_printf (lines, " %08x", + msg->video_formats->list->H264_codec.misc_params.CEA_Support); + g_string_append_printf (lines, " %08x", + msg->video_formats->list->H264_codec.misc_params.VESA_Support); + g_string_append_printf (lines, " %08x", + msg->video_formats->list->H264_codec.misc_params.HH_Support); + g_string_append_printf (lines, " %02x", + msg->video_formats->list->H264_codec.misc_params.latency); + g_string_append_printf (lines, " %04x", + msg->video_formats->list->H264_codec.misc_params.min_slice_size); + g_string_append_printf (lines, " %04x", + msg->video_formats->list->H264_codec.misc_params.slice_enc_params); + g_string_append_printf (lines, " %02x", + msg->video_formats->list->H264_codec. + misc_params.frame_rate_control_support); + + if (msg->video_formats->list->H264_codec.max_hres) + g_string_append_printf (lines, " %04x", + msg->video_formats->list->H264_codec.max_hres); + else + g_string_append_printf (lines, " none"); + + if (msg->video_formats->list->H264_codec.max_vres) + g_string_append_printf (lines, " %04x", + msg->video_formats->list->H264_codec.max_vres); + else + g_string_append_printf (lines, " none"); + } + g_string_append_printf (lines, "\r\n"); + } + + /* list of video 3D codecs */ + if (msg->video_3d_formats) { + g_string_append_printf (lines, GST_STRING_WFD_3D_VIDEO_FORMATS); + g_string_append_printf (lines, ":"); + if (msg->video_3d_formats->list) { + g_string_append_printf (lines, " %02x", + msg->video_3d_formats->list->native); + g_string_append_printf (lines, " %02x", + msg->video_3d_formats->list->preferred_display_mode_supported); + g_string_append_printf (lines, " %02x", + msg->video_3d_formats->list->H264_codec.profile); + g_string_append_printf (lines, " %02x", + msg->video_3d_formats->list->H264_codec.level); + g_string_append_printf (lines, " %16x", + msg->video_3d_formats->list->H264_codec. + misc_params.video_3d_capability); + g_string_append_printf (lines, " %02x", + msg->video_3d_formats->list->H264_codec.misc_params.latency); + g_string_append_printf (lines, " %04x", + msg->video_3d_formats->list->H264_codec.misc_params.min_slice_size); + g_string_append_printf (lines, " %04x", + msg->video_3d_formats->list->H264_codec.misc_params.slice_enc_params); + g_string_append_printf (lines, " %02x", + msg->video_3d_formats->list->H264_codec. + misc_params.frame_rate_control_support); + if (msg->video_3d_formats->list->H264_codec.max_hres) + g_string_append_printf (lines, " %04x", + msg->video_3d_formats->list->H264_codec.max_hres); + else + g_string_append_printf (lines, " none"); + if (msg->video_3d_formats->list->H264_codec.max_vres) + g_string_append_printf (lines, " %04x", + msg->video_3d_formats->list->H264_codec.max_vres); + else + g_string_append_printf (lines, " none"); + } else { + g_string_append_printf (lines, " none"); + } + g_string_append_printf (lines, "\r\n"); + } + + if (msg->content_protection) { + g_string_append_printf (lines, GST_STRING_WFD_CONTENT_PROTECTION); + g_string_append_printf (lines, ":"); + if (msg->content_protection->hdcp2_spec) { + if (msg->content_protection->hdcp2_spec->hdcpversion) { + g_string_append_printf (lines, " %s", + msg->content_protection->hdcp2_spec->hdcpversion); + g_string_append_printf (lines, " %s", + msg->content_protection->hdcp2_spec->TCPPort); + } else { + g_string_append_printf (lines, " none"); + } + } else { + g_string_append_printf (lines, " none"); + } + g_string_append_printf (lines, "\r\n"); + } + + if (msg->display_edid) { + g_string_append_printf (lines, GST_STRING_WFD_DISPLAY_EDID); + g_string_append_printf (lines, ":"); + if (msg->display_edid->edid_supported) { + if (msg->display_edid->edid_block_count > 0 && + msg->display_edid->edid_block_count <= EDID_BLOCK_COUNT_MAX_SIZE) { + g_string_append_printf (lines," %04x", msg->display_edid->edid_block_count); + g_string_append_printf(lines, " %s", msg->display_edid->edid_payload); + } else + g_string_append_printf (lines, " none"); + } else { + g_string_append_printf (lines, " none"); + } + g_string_append_printf (lines, "\r\n"); + } + + if (msg->coupled_sink) { + g_string_append_printf (lines, GST_STRING_WFD_COUPLED_SINK); + g_string_append_printf (lines, ":"); + if (msg->coupled_sink->coupled_sink_cap) { + g_string_append_printf (lines, " %02x", + msg->coupled_sink->coupled_sink_cap->status); + if (msg->coupled_sink->coupled_sink_cap->sink_address) + g_string_append_printf (lines, " %s", + msg->coupled_sink->coupled_sink_cap->sink_address); + else + g_string_append_printf (lines, " none"); + } else { + g_string_append_printf (lines, " none"); + } + g_string_append_printf (lines, "\r\n"); + } + + if (msg->trigger_method) { + g_string_append_printf (lines, GST_STRING_WFD_TRIGGER_METHOD); + g_string_append_printf (lines, ":"); + g_string_append_printf (lines, " %s", + msg->trigger_method->wfd_trigger_method); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->presentation_url) { + g_string_append_printf (lines, GST_STRING_WFD_PRESENTATION_URL); + g_string_append_printf (lines, ":"); + if (msg->presentation_url->wfd_url0) + g_string_append_printf (lines, " %s", msg->presentation_url->wfd_url0); + else + g_string_append_printf (lines, " none"); + if (msg->presentation_url->wfd_url1) + g_string_append_printf (lines, " %s", msg->presentation_url->wfd_url1); + else + g_string_append_printf (lines, " none"); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->client_rtp_ports) { + g_string_append_printf (lines, GST_STRING_WFD_CLIENT_RTP_PORTS); + if (msg->client_rtp_ports->profile) { + g_string_append_printf (lines, ":"); + g_string_append_printf (lines, " %s", msg->client_rtp_ports->profile); + g_string_append_printf (lines, " %d", msg->client_rtp_ports->rtp_port0); + g_string_append_printf (lines, " %d", msg->client_rtp_ports->rtp_port1); + g_string_append_printf (lines, " %s", msg->client_rtp_ports->mode); + } + g_string_append_printf (lines, "\r\n"); + } + + if (msg->route) { + g_string_append_printf (lines, GST_STRING_WFD_ROUTE); + g_string_append_printf (lines, ":"); + g_string_append_printf (lines, " %s", msg->route->destination); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->I2C) { + g_string_append_printf (lines, GST_STRING_WFD_I2C); + g_string_append_printf (lines, ":"); + if (msg->I2C->I2CPresent) + g_string_append_printf (lines, " %x", msg->I2C->I2C_port); + else + g_string_append_printf (lines, " none"); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->av_format_change_timing) { + g_string_append_printf (lines, GST_STRING_WFD_AV_FORMAT_CHANGE_TIMING); + g_string_append_printf (lines, ":"); + g_string_append_printf (lines, " %010llx", + msg->av_format_change_timing->PTS); + g_string_append_printf (lines, " %010llx", + msg->av_format_change_timing->DTS); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->preferred_display_mode) { + g_string_append_printf (lines, GST_STRING_WFD_PREFERRED_DISPLAY_MODE); + g_string_append_printf (lines, ":"); + if (msg->preferred_display_mode->displaymodesupported) { + g_string_append_printf (lines, " %06llx", + msg->preferred_display_mode->p_clock); + g_string_append_printf (lines, " %04x", msg->preferred_display_mode->H); + g_string_append_printf (lines, " %04x", msg->preferred_display_mode->HB); + g_string_append_printf (lines, " %04x", + msg->preferred_display_mode->HSPOL_HSOFF); + g_string_append_printf (lines, " %04x", msg->preferred_display_mode->HSW); + g_string_append_printf (lines, " %04x", msg->preferred_display_mode->V); + g_string_append_printf (lines, " %04x", msg->preferred_display_mode->VB); + g_string_append_printf (lines, " %04x", + msg->preferred_display_mode->VSPOL_VSOFF); + g_string_append_printf (lines, " %04x", msg->preferred_display_mode->VSW); + g_string_append_printf (lines, " %02x", + msg->preferred_display_mode->VBS3D); + g_string_append_printf (lines, " %02x", + msg->preferred_display_mode->V2d_s3d_modes); + g_string_append_printf (lines, " %02x", + msg->preferred_display_mode->P_depth); + } else + g_string_append_printf (lines, " none"); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->standby_resume_capability) { + g_string_append_printf (lines, GST_STRING_WFD_STANDBY_RESUME_CAPABILITY); + g_string_append_printf (lines, ":"); + if (msg->standby_resume_capability->standby_resume_cap) + g_string_append_printf (lines, " supported"); + else + g_string_append_printf (lines, " none"); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->standby) { + g_string_append_printf (lines, GST_STRING_WFD_STANDBY); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->connector_type) { + g_string_append_printf (lines, GST_STRING_WFD_CONNECTOR_TYPE); + g_string_append_printf (lines, ":"); + if (msg->connector_type->connector_type) + g_string_append_printf (lines, " %02x", + msg->connector_type->connector_type); + else + g_string_append_printf (lines, " none"); + g_string_append_printf (lines, "\r\n"); + } + + if (msg->idr_request) { + g_string_append_printf (lines, GST_STRING_WFD_IDR_REQUEST); + g_string_append_printf (lines, "\r\n"); + } + + return g_string_free (lines, FALSE); +} + +gchar * +gst_wfd_message_param_names_as_text (const GstWFDMessage * msg) +{ + /* change all vars so they match rfc? */ + GString *lines; + g_return_val_if_fail (msg != NULL, NULL); + + lines = g_string_new (""); + + /* list of audio codecs */ + if (msg->audio_codecs) { + g_string_append_printf (lines, GST_STRING_WFD_AUDIO_CODECS); + g_string_append_printf (lines, "\r\n"); + } + /* list of video codecs */ + if (msg->video_formats) { + g_string_append_printf (lines, GST_STRING_WFD_VIDEO_FORMATS); + g_string_append_printf (lines, "\r\n"); + } + /* list of video 3D codecs */ + if (msg->video_3d_formats) { + g_string_append_printf (lines, GST_STRING_WFD_3D_VIDEO_FORMATS); + g_string_append_printf (lines, "\r\n"); + } + if (msg->content_protection) { + g_string_append_printf (lines, GST_STRING_WFD_CONTENT_PROTECTION); + g_string_append_printf (lines, "\r\n"); + } + if (msg->display_edid) { + g_string_append_printf (lines, GST_STRING_WFD_DISPLAY_EDID); + g_string_append_printf (lines, "\r\n"); + } + if (msg->coupled_sink) { + g_string_append_printf (lines, GST_STRING_WFD_COUPLED_SINK); + g_string_append_printf (lines, "\r\n"); + } + if (msg->trigger_method) { + g_string_append_printf (lines, GST_STRING_WFD_TRIGGER_METHOD); + g_string_append_printf (lines, "\r\n"); + } + if (msg->presentation_url) { + g_string_append_printf (lines, GST_STRING_WFD_PRESENTATION_URL); + g_string_append_printf (lines, "\r\n"); + } + if (msg->client_rtp_ports) { + g_string_append_printf (lines, GST_STRING_WFD_CLIENT_RTP_PORTS); + g_string_append_printf (lines, "\r\n"); + } + if (msg->route) { + g_string_append_printf (lines, GST_STRING_WFD_ROUTE); + g_string_append_printf (lines, "\r\n"); + } + if (msg->I2C) { + g_string_append_printf (lines, GST_STRING_WFD_I2C); + g_string_append_printf (lines, "\r\n"); + } + if (msg->av_format_change_timing) { + g_string_append_printf (lines, GST_STRING_WFD_AV_FORMAT_CHANGE_TIMING); + g_string_append_printf (lines, "\r\n"); + } + if (msg->preferred_display_mode) { + g_string_append_printf (lines, GST_STRING_WFD_PREFERRED_DISPLAY_MODE); + g_string_append_printf (lines, "\r\n"); + } + if (msg->standby_resume_capability) { + g_string_append_printf (lines, GST_STRING_WFD_STANDBY_RESUME_CAPABILITY); + g_string_append_printf (lines, "\r\n"); + } + if (msg->standby) { + g_string_append_printf (lines, GST_STRING_WFD_STANDBY); + g_string_append_printf (lines, "\r\n"); + } + if (msg->connector_type) { + g_string_append_printf (lines, GST_STRING_WFD_CONNECTOR_TYPE); + g_string_append_printf (lines, "\r\n"); + } + if (msg->idr_request) { + g_string_append_printf (lines, GST_STRING_WFD_IDR_REQUEST); + g_string_append_printf (lines, "\r\n"); + } + + return g_string_free (lines, FALSE); +} + +/** + * gst_wfd_message_dump: + * @msg: a #GstWFDMessage + * + * Dump the parsed contents of @msg to stdout. + * + * Returns: a #GstWFDResult. + */ +GstWFDResult +gst_wfd_message_dump (const GstWFDMessage * msg) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (msg->audio_codecs) { + guint i = 0; + g_print ("Audio supported formats : \n"); + for (; i < msg->audio_codecs->count; i++) { + g_print ("Codec: %s\n", msg->audio_codecs->list[i].audio_format); + if (!strcmp (msg->audio_codecs->list[i].audio_format, "LPCM")) { + if (msg->audio_codecs->list[i].modes & GST_WFD_FREQ_44100) + g_print (" Freq: %d\n", 44100); + if (msg->audio_codecs->list[i].modes & GST_WFD_FREQ_48000) + g_print (" Freq: %d\n", 48000); + g_print (" Channels: %d\n", 2); + } + if (!strcmp (msg->audio_codecs->list[i].audio_format, "AAC")) { + g_print (" Freq: %d\n", 48000); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_2) + g_print (" Channels: %d\n", 2); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_4) + g_print (" Channels: %d\n", 4); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_6) + g_print (" Channels: %d\n", 6); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_8) + g_print (" Channels: %d\n", 8); + } + if (!strcmp (msg->audio_codecs->list[i].audio_format, "AC3")) { + g_print (" Freq: %d\n", 48000); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_2) + g_print (" Channels: %d\n", 2); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_4) + g_print (" Channels: %d\n", 4); + if (msg->audio_codecs->list[i].modes & GST_WFD_CHANNEL_6) + g_print (" Channels: %d\n", 6); + } + g_print (" Bitwidth: %d\n", 16); + g_print (" Latency: %d\n", msg->audio_codecs->list[i].latency); + } + } + + + if (msg->video_formats) { + g_print ("Video supported formats : \n"); + if (msg->video_formats->list) { + guint nativeindex = 0; + g_print ("Codec: H264\n"); + if ((msg->video_formats->list->native & 0x7) == + GST_WFD_VIDEO_CEA_RESOLUTION) { + g_print (" Native type: CEA\n"); + } else if ((msg->video_formats->list->native & 0x7) == + GST_WFD_VIDEO_VESA_RESOLUTION) { + g_print (" Native type: VESA\n"); + } else if ((msg->video_formats->list->native & 0x7) == + GST_WFD_VIDEO_HH_RESOLUTION) { + g_print (" Native type: HH\n"); + } + nativeindex = msg->video_formats->list->native >> 3; + g_print (" Resolution: %d\n", (1 << nativeindex)); + + if (msg->video_formats->list-> + H264_codec.profile & GST_WFD_H264_BASE_PROFILE) { + g_print (" Profile: BASE\n"); + } else if (msg->video_formats->list-> + H264_codec.profile & GST_WFD_H264_HIGH_PROFILE) { + g_print (" Profile: HIGH\n"); + } + if (msg->video_formats->list->H264_codec.level & GST_WFD_H264_LEVEL_3_1) { + g_print (" Level: 3.1\n"); + } else if (msg->video_formats->list-> + H264_codec.level & GST_WFD_H264_LEVEL_3_2) { + g_print (" Level: 3.2\n"); + } else if (msg->video_formats->list-> + H264_codec.level & GST_WFD_H264_LEVEL_4) { + g_print (" Level: 4\n"); + } else if (msg->video_formats->list-> + H264_codec.level & GST_WFD_H264_LEVEL_4_1) { + g_print (" Level: 4.1\n"); + } else if (msg->video_formats->list-> + H264_codec.level & GST_WFD_H264_LEVEL_4_2) { + g_print (" Level: 4.2\n"); + } + g_print (" Latency: %d\n", + msg->video_formats->list->H264_codec.misc_params.latency); + g_print (" min_slice_size: %x\n", + msg->video_formats->list->H264_codec.misc_params.min_slice_size); + g_print (" slice_enc_params: %x\n", + msg->video_formats->list->H264_codec.misc_params.slice_enc_params); + g_print (" frame_rate_control_support: %x\n", + msg->video_formats->list->H264_codec. + misc_params.frame_rate_control_support); + if (msg->video_formats->list->H264_codec.max_hres) { + g_print (" Max Height: %04d\n", + msg->video_formats->list->H264_codec.max_hres); + } + if (msg->video_formats->list->H264_codec.max_vres) { + g_print (" Max Width: %04d\n", + msg->video_formats->list->H264_codec.max_vres); + } + } + } + + if (msg->video_3d_formats) { + g_print ("wfd_3d_formats"); + g_print ("\r\n"); + } + + if (msg->content_protection) { + g_print (GST_STRING_WFD_CONTENT_PROTECTION); + g_print ("\r\n"); + } + + if (msg->display_edid) { + g_print (GST_STRING_WFD_DISPLAY_EDID); + g_print ("\r\n"); + } + + if (msg->coupled_sink) { + g_print (GST_STRING_WFD_COUPLED_SINK); + g_print ("\r\n"); + } + + if (msg->trigger_method) { + g_print (" Trigger type: %s\n", msg->trigger_method->wfd_trigger_method); + } + + if (msg->presentation_url) { + g_print (GST_STRING_WFD_PRESENTATION_URL); + g_print ("\r\n"); + } + + if (msg->client_rtp_ports) { + g_print (" Client RTP Ports : \n"); + if (msg->client_rtp_ports->profile) { + g_print ("%s\n", msg->client_rtp_ports->profile); + g_print (" %d\n", msg->client_rtp_ports->rtp_port0); + g_print (" %d\n", msg->client_rtp_ports->rtp_port1); + g_print (" %s\n", msg->client_rtp_ports->mode); + } + g_print ("\r\n"); + } + + if (msg->route) { + g_print (GST_STRING_WFD_ROUTE); + g_print ("\r\n"); + } + + if (msg->I2C) { + g_print (GST_STRING_WFD_I2C); + g_print ("\r\n"); + } + + if (msg->av_format_change_timing) { + g_print (GST_STRING_WFD_AV_FORMAT_CHANGE_TIMING); + g_print ("\r\n"); + } + + if (msg->preferred_display_mode) { + g_print (GST_STRING_WFD_PREFERRED_DISPLAY_MODE); + g_print ("\r\n"); + } + + if (msg->standby_resume_capability) { + g_print (GST_STRING_WFD_STANDBY_RESUME_CAPABILITY); + g_print ("\r\n"); + } + + if (msg->standby) { + g_print (GST_STRING_WFD_STANDBY); + g_print ("\r\n"); + } + + if (msg->connector_type) { + g_print (GST_STRING_WFD_CONNECTOR_TYPE); + g_print ("\r\n"); + } + + if (msg->idr_request) { + g_print (GST_STRING_WFD_IDR_REQUEST); + g_print ("\r\n"); + } + + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_supported_audio_format (GstWFDMessage * msg, + GstWFDAudioFormats a_codec, + guint a_freq, guint a_channels, guint a_bitwidth, guint32 a_latency) +{ + guint temp = a_codec; + guint i = 0; + guint pcm = 0, aac = 0, ac3 = 0; + + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!msg->audio_codecs) + msg->audio_codecs = g_new0 (GstWFDAudioCodeclist, 1); + + if (a_codec != GST_WFD_AUDIO_UNKNOWN) { + while (temp) { + msg->audio_codecs->count++; + temp >>= 1; + } + msg->audio_codecs->list = + g_new0 (GstWFDAudioCodec, msg->audio_codecs->count); + for (; i < msg->audio_codecs->count; i++) { + if ((a_codec & GST_WFD_AUDIO_LPCM) && (!pcm)) { + msg->audio_codecs->list[i].audio_format = g_strdup ("LPCM"); + msg->audio_codecs->list[i].modes = a_freq; + msg->audio_codecs->list[i].latency = a_latency; + pcm = 1; + } else if ((a_codec & GST_WFD_AUDIO_AAC) && (!aac)) { + msg->audio_codecs->list[i].audio_format = g_strdup ("AAC"); + msg->audio_codecs->list[i].modes = a_channels; + msg->audio_codecs->list[i].latency = a_latency; + aac = 1; + } else if ((a_codec & GST_WFD_AUDIO_AC3) && (!ac3)) { + msg->audio_codecs->list[i].audio_format = g_strdup ("AC3"); + msg->audio_codecs->list[i].modes = a_channels; + msg->audio_codecs->list[i].latency = a_latency; + ac3 = 1; + } + } + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_prefered_audio_format (GstWFDMessage * msg, + GstWFDAudioFormats a_codec, + GstWFDAudioFreq a_freq, + GstWFDAudioChannels a_channels, guint a_bitwidth, guint32 a_latency) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!msg->audio_codecs) + msg->audio_codecs = g_new0 (GstWFDAudioCodeclist, 1); + + msg->audio_codecs->list = g_new0 (GstWFDAudioCodec, 1); + msg->audio_codecs->count = 1; + if (a_codec == GST_WFD_AUDIO_LPCM) { + msg->audio_codecs->list->audio_format = g_strdup ("LPCM"); + msg->audio_codecs->list->modes = a_freq; + msg->audio_codecs->list->latency = a_latency; + } else if (a_codec == GST_WFD_AUDIO_AAC) { + msg->audio_codecs->list->audio_format = g_strdup ("AAC"); + msg->audio_codecs->list->modes = a_channels; + msg->audio_codecs->list->latency = a_latency; + } else if (a_codec == GST_WFD_AUDIO_AC3) { + msg->audio_codecs->list->audio_format = g_strdup ("AC3"); + msg->audio_codecs->list->modes = a_channels; + msg->audio_codecs->list->latency = a_latency; + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_supported_audio_format (GstWFDMessage * msg, + guint * a_codec, + guint * a_freq, guint * a_channels, guint * a_bitwidth, guint32 * a_latency) +{ + guint i = 0; + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (msg->audio_codecs != NULL, GST_WFD_EINVAL); + + for (; i < msg->audio_codecs->count; i++) { + if (!g_strcmp0 (msg->audio_codecs->list[i].audio_format, "LPCM")) { + *a_codec |= GST_WFD_AUDIO_LPCM; + *a_freq |= msg->audio_codecs->list[i].modes; + *a_channels |= GST_WFD_CHANNEL_2; + *a_bitwidth = 16; + *a_latency = msg->audio_codecs->list[i].latency; + } else if (!g_strcmp0 (msg->audio_codecs->list[i].audio_format, "AAC")) { + *a_codec |= GST_WFD_AUDIO_AAC; + *a_freq |= GST_WFD_FREQ_48000; + *a_channels |= msg->audio_codecs->list[i].modes; + *a_bitwidth = 16; + *a_latency = msg->audio_codecs->list[i].latency; + } else if (!g_strcmp0 (msg->audio_codecs->list[i].audio_format, "AC3")) { + *a_codec |= GST_WFD_AUDIO_AC3; + *a_freq |= GST_WFD_FREQ_48000; + *a_channels |= msg->audio_codecs->list[i].modes; + *a_bitwidth = 16; + *a_latency = msg->audio_codecs->list[i].latency; + } + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_prefered_audio_format (GstWFDMessage * msg, + GstWFDAudioFormats * a_codec, + GstWFDAudioFreq * a_freq, + GstWFDAudioChannels * a_channels, guint * a_bitwidth, guint32 * a_latency) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!g_strcmp0 (msg->audio_codecs->list->audio_format, "LPCM")) { + *a_codec = GST_WFD_AUDIO_LPCM; + *a_freq = msg->audio_codecs->list->modes; + *a_channels = GST_WFD_CHANNEL_2; + *a_bitwidth = 16; + *a_latency = msg->audio_codecs->list->latency; + } else if (!g_strcmp0 (msg->audio_codecs->list->audio_format, "AAC")) { + *a_codec = GST_WFD_AUDIO_AAC; + *a_freq = GST_WFD_FREQ_48000; + *a_channels = msg->audio_codecs->list->modes; + *a_bitwidth = 16; + *a_latency = msg->audio_codecs->list->latency; + } else if (!g_strcmp0 (msg->audio_codecs->list->audio_format, "AC3")) { + *a_codec = GST_WFD_AUDIO_AC3; + *a_freq = GST_WFD_FREQ_48000; + *a_channels = msg->audio_codecs->list->modes; + *a_bitwidth = 16; + *a_latency = msg->audio_codecs->list->latency; + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_supported_video_format (GstWFDMessage * msg, + GstWFDVideoCodecs v_codec, + GstWFDVideoNativeResolution v_native, + guint64 v_native_resolution, + guint64 v_cea_resolution, + guint64 v_vesa_resolution, + guint64 v_hh_resolution, + guint v_profile, + guint v_level, + guint32 v_latency, + guint32 v_max_height, + guint32 v_max_width, + guint32 min_slice_size, guint32 slice_enc_params, guint frame_rate_control) +{ + guint nativeindex = 0; + guint64 temp = v_native_resolution; + + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!msg->video_formats) + msg->video_formats = g_new0 (GstWFDVideoCodeclist, 1); + + if (v_codec != GST_WFD_VIDEO_UNKNOWN) { + msg->video_formats->list = g_new0 (GstWFDVideoCodec, 1); + while (temp) { + nativeindex++; + temp >>= 1; + } + + msg->video_formats->list->native = nativeindex - 1; + msg->video_formats->list->native <<= 3; + + if (v_native == GST_WFD_VIDEO_VESA_RESOLUTION) + msg->video_formats->list->native |= 1; + else if (v_native == GST_WFD_VIDEO_HH_RESOLUTION) + msg->video_formats->list->native |= 2; + + msg->video_formats->list->preferred_display_mode_supported = 1; + msg->video_formats->list->H264_codec.profile = v_profile; + msg->video_formats->list->H264_codec.level = v_level; + msg->video_formats->list->H264_codec.max_hres = v_max_height; + msg->video_formats->list->H264_codec.max_vres = v_max_width; + msg->video_formats->list->H264_codec.misc_params.CEA_Support = + v_cea_resolution; + msg->video_formats->list->H264_codec.misc_params.VESA_Support = + v_vesa_resolution; + msg->video_formats->list->H264_codec.misc_params.HH_Support = + v_hh_resolution; + msg->video_formats->list->H264_codec.misc_params.latency = v_latency; + msg->video_formats->list->H264_codec.misc_params.min_slice_size = + min_slice_size; + msg->video_formats->list->H264_codec.misc_params.slice_enc_params = + slice_enc_params; + msg->video_formats->list->H264_codec. + misc_params.frame_rate_control_support = frame_rate_control; + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_prefered_video_format (GstWFDMessage * msg, + GstWFDVideoCodecs v_codec, + GstWFDVideoNativeResolution v_native, + guint64 v_native_resolution, + GstWFDVideoCEAResolution v_cea_resolution, + GstWFDVideoVESAResolution v_vesa_resolution, + GstWFDVideoHHResolution v_hh_resolution, + GstWFDVideoH264Profile v_profile, + GstWFDVideoH264Level v_level, + guint32 v_latency, + guint32 v_max_height, + guint32 v_max_width, + guint32 min_slice_size, guint32 slice_enc_params, guint frame_rate_control) +{ + guint nativeindex = 0; + guint64 temp = v_native_resolution; + + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!msg->video_formats) + msg->video_formats = g_new0 (GstWFDVideoCodeclist, 1); + msg->video_formats->list = g_new0 (GstWFDVideoCodec, 1); + + while (temp) { + nativeindex++; + temp >>= 1; + } + + if (nativeindex) + msg->video_formats->list->native = nativeindex - 1; + msg->video_formats->list->native <<= 3; + + if (v_native == GST_WFD_VIDEO_VESA_RESOLUTION) + msg->video_formats->list->native |= 1; + else if (v_native == GST_WFD_VIDEO_HH_RESOLUTION) + msg->video_formats->list->native |= 2; + + msg->video_formats->list->preferred_display_mode_supported = 0; + msg->video_formats->list->H264_codec.profile = v_profile; + msg->video_formats->list->H264_codec.level = v_level; + msg->video_formats->list->H264_codec.max_hres = v_max_height; + msg->video_formats->list->H264_codec.max_vres = v_max_width; + msg->video_formats->list->H264_codec.misc_params.CEA_Support = + v_cea_resolution; + msg->video_formats->list->H264_codec.misc_params.VESA_Support = + v_vesa_resolution; + msg->video_formats->list->H264_codec.misc_params.HH_Support = v_hh_resolution; + msg->video_formats->list->H264_codec.misc_params.latency = v_latency; + msg->video_formats->list->H264_codec.misc_params.min_slice_size = + min_slice_size; + msg->video_formats->list->H264_codec.misc_params.slice_enc_params = + slice_enc_params; + msg->video_formats->list->H264_codec.misc_params.frame_rate_control_support = + frame_rate_control; + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_supported_video_format (GstWFDMessage * msg, + GstWFDVideoCodecs * v_codec, + GstWFDVideoNativeResolution * v_native, + guint64 * v_native_resolution, + guint64 * v_cea_resolution, + guint64 * v_vesa_resolution, + guint64 * v_hh_resolution, + guint * v_profile, + guint * v_level, + guint32 * v_latency, + guint32 * v_max_height, + guint32 * v_max_width, + guint32 * min_slice_size, + guint32 * slice_enc_params, guint * frame_rate_control) +{ + guint nativeindex = 0; + + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + *v_codec = GST_WFD_VIDEO_H264; + *v_native = msg->video_formats->list->native & 0x7; + nativeindex = msg->video_formats->list->native >> 3; + *v_native_resolution = ((guint64) 1) << nativeindex; + *v_profile = msg->video_formats->list->H264_codec.profile; + *v_level = msg->video_formats->list->H264_codec.level; + *v_max_height = msg->video_formats->list->H264_codec.max_hres; + *v_max_width = msg->video_formats->list->H264_codec.max_vres; + *v_cea_resolution = + msg->video_formats->list->H264_codec.misc_params.CEA_Support; + *v_vesa_resolution = + msg->video_formats->list->H264_codec.misc_params.VESA_Support; + *v_hh_resolution = + msg->video_formats->list->H264_codec.misc_params.HH_Support; + *v_latency = msg->video_formats->list->H264_codec.misc_params.latency; + *min_slice_size = + msg->video_formats->list->H264_codec.misc_params.min_slice_size; + *slice_enc_params = + msg->video_formats->list->H264_codec.misc_params.slice_enc_params; + *frame_rate_control = + msg->video_formats->list->H264_codec. + misc_params.frame_rate_control_support; + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_prefered_video_format (GstWFDMessage * msg, + GstWFDVideoCodecs * v_codec, + GstWFDVideoNativeResolution * v_native, + guint64 * v_native_resolution, + GstWFDVideoCEAResolution * v_cea_resolution, + GstWFDVideoVESAResolution * v_vesa_resolution, + GstWFDVideoHHResolution * v_hh_resolution, + GstWFDVideoH264Profile * v_profile, + GstWFDVideoH264Level * v_level, + guint32 * v_latency, + guint32 * v_max_height, + guint32 * v_max_width, + guint32 * min_slice_size, + guint32 * slice_enc_params, guint * frame_rate_control) +{ + guint nativeindex = 0; + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + *v_codec = GST_WFD_VIDEO_H264; + *v_native = msg->video_formats->list->native & 0x7; + nativeindex = msg->video_formats->list->native >> 3; + *v_native_resolution = ((guint64) 1) << nativeindex; + *v_profile = msg->video_formats->list->H264_codec.profile; + *v_level = msg->video_formats->list->H264_codec.level; + *v_max_height = msg->video_formats->list->H264_codec.max_hres; + *v_max_width = msg->video_formats->list->H264_codec.max_vres; + *v_cea_resolution = + msg->video_formats->list->H264_codec.misc_params.CEA_Support; + *v_vesa_resolution = + msg->video_formats->list->H264_codec.misc_params.VESA_Support; + *v_hh_resolution = + msg->video_formats->list->H264_codec.misc_params.HH_Support; + *v_latency = msg->video_formats->list->H264_codec.misc_params.latency; + *min_slice_size = + msg->video_formats->list->H264_codec.misc_params.min_slice_size; + *slice_enc_params = + msg->video_formats->list->H264_codec.misc_params.slice_enc_params; + *frame_rate_control = + msg->video_formats->list->H264_codec. + misc_params.frame_rate_control_support; + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_display_edid (GstWFDMessage * msg, + gboolean edid_supported, guint32 edid_blockcount, gchar * edid_playload) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + if (!msg->display_edid) + msg->display_edid = g_new0 (GstWFDDisplayEdid, 1); + msg->display_edid->edid_supported = edid_supported; + if (!edid_supported) + return GST_WFD_OK; + if (edid_blockcount > 0 && edid_blockcount <= EDID_BLOCK_COUNT_MAX_SIZE) { + msg->display_edid->edid_block_count = edid_blockcount; + msg->display_edid->edid_payload = g_malloc(EDID_BLOCK_SIZE * edid_blockcount); + if (msg->display_edid->edid_payload) + memcpy(msg->display_edid->edid_payload, edid_playload, EDID_BLOCK_SIZE * edid_blockcount); + else + msg->display_edid->edid_supported = FALSE; + } else + msg->display_edid->edid_supported = FALSE; + + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_display_edid (GstWFDMessage * msg, + gboolean * edid_supported, + guint32 * edid_blockcount, gchar ** edid_playload) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (edid_supported != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (edid_blockcount != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (edid_playload != NULL, GST_WFD_EINVAL); + + *edid_supported = FALSE; + if (msg->display_edid) { + if (msg->display_edid->edid_supported) { + *edid_blockcount = msg->display_edid->edid_block_count; + if (msg->display_edid->edid_block_count > 0 && msg->display_edid->edid_block_count <= EDID_BLOCK_COUNT_MAX_SIZE) { + char *temp; + temp = g_malloc0(EDID_BLOCK_SIZE * msg->display_edid->edid_block_count); + if (temp) { + memcpy(temp, msg->display_edid->edid_payload, EDID_BLOCK_SIZE * msg->display_edid->edid_block_count); + *edid_playload = temp; + *edid_supported = TRUE; + } + } + } + } + return GST_WFD_OK; +} + + +GstWFDResult +gst_wfd_message_set_contentprotection_type (GstWFDMessage * msg, + GstWFDHDCPProtection hdcpversion, guint32 TCPPort) +{ + char str[11] = { 0, }; + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (TCPPort <= MAX_PORT_SIZE, GST_WFD_EINVAL); + + if (!msg->content_protection) + msg->content_protection = g_new0 (GstWFDContentProtection, 1); + if (hdcpversion == GST_WFD_HDCP_NONE) + return GST_WFD_OK; + msg->content_protection->hdcp2_spec = g_new0 (GstWFDHdcp2Spec, 1); + if (hdcpversion == GST_WFD_HDCP_2_0) + msg->content_protection->hdcp2_spec->hdcpversion = g_strdup ("HDCP2.0"); + else if (hdcpversion == GST_WFD_HDCP_2_1) + msg->content_protection->hdcp2_spec->hdcpversion = g_strdup ("HDCP2.1"); + snprintf (str, sizeof (str), "port=%d", TCPPort); + msg->content_protection->hdcp2_spec->TCPPort = g_strdup (str); + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_contentprotection_type (GstWFDMessage * msg, + GstWFDHDCPProtection * hdcpversion, guint32 * TCPPort) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + if (msg->content_protection && msg->content_protection->hdcp2_spec) { + char *result = NULL; + char *ptr = NULL; + if (!g_strcmp0 (msg->content_protection->hdcp2_spec->hdcpversion, "none")) { + *hdcpversion = GST_WFD_HDCP_NONE; + *TCPPort = 0; + return GST_WFD_OK; + } + if (!g_strcmp0 (msg->content_protection->hdcp2_spec->hdcpversion, + "HDCP2.0")) + *hdcpversion = GST_WFD_HDCP_2_0; + else if (!g_strcmp0 (msg->content_protection->hdcp2_spec->hdcpversion, + "HDCP2.1")) + *hdcpversion = GST_WFD_HDCP_2_1; + else { + *hdcpversion = GST_WFD_HDCP_NONE; + *TCPPort = 0; + return GST_WFD_OK; + } + + result = strtok_r (msg->content_protection->hdcp2_spec->TCPPort, "=", &ptr); + while (result != NULL) { + result = strtok_r (NULL, "=", &ptr); + *TCPPort = atoi (result); + break; + } + } else + *hdcpversion = GST_WFD_HDCP_NONE; + return GST_WFD_OK; +} + + +GstWFDResult +gst_wfd_messge_set_prefered_rtp_ports (GstWFDMessage * msg, + GstWFDRTSPTransMode trans, + GstWFDRTSPProfile profile, + GstWFDRTSPLowerTrans lowertrans, guint32 rtp_port0, guint32 rtp_port1) +{ + GString *lines; + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!msg->client_rtp_ports) + msg->client_rtp_ports = g_new0 (GstWFDClientRtpPorts, 1); + + if (trans != GST_WFD_RTSP_TRANS_UNKNOWN) { + lines = g_string_new (""); + if (trans == GST_WFD_RTSP_TRANS_RTP) + g_string_append_printf (lines, "RTP"); + else if (trans == GST_WFD_RTSP_TRANS_RDT) + g_string_append_printf (lines, "RDT"); + + if (profile == GST_WFD_RTSP_PROFILE_AVP) + g_string_append_printf (lines, "/AVP"); + else if (profile == GST_WFD_RTSP_PROFILE_SAVP) + g_string_append_printf (lines, "/SAVP"); + + if (lowertrans == GST_WFD_RTSP_LOWER_TRANS_UDP) + g_string_append_printf (lines, "/UDP;unicast"); + else if (lowertrans == GST_WFD_RTSP_LOWER_TRANS_UDP_MCAST) + g_string_append_printf (lines, "/UDP;multicast"); + else if (lowertrans == GST_WFD_RTSP_LOWER_TRANS_TCP) + g_string_append_printf (lines, "/TCP;unicast"); + else if (lowertrans == GST_WFD_RTSP_LOWER_TRANS_HTTP) + g_string_append_printf (lines, "/HTTP"); + + msg->client_rtp_ports->profile = g_string_free (lines, FALSE); + msg->client_rtp_ports->rtp_port0 = rtp_port0; + msg->client_rtp_ports->rtp_port1 = rtp_port1; + msg->client_rtp_ports->mode = g_strdup ("mode=play"); + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_prefered_rtp_ports (GstWFDMessage * msg, + GstWFDRTSPTransMode * trans, + GstWFDRTSPProfile * profile, + GstWFDRTSPLowerTrans * lowertrans, guint32 * rtp_port0, guint32 * rtp_port1) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + g_return_val_if_fail (msg->client_rtp_ports != NULL, GST_WFD_EINVAL); + + if (g_strrstr (msg->client_rtp_ports->profile, "RTP")) + *trans = GST_WFD_RTSP_TRANS_RTP; + if (g_strrstr (msg->client_rtp_ports->profile, "RDT")) + *trans = GST_WFD_RTSP_TRANS_RDT; + if (g_strrstr (msg->client_rtp_ports->profile, "AVP")) + *profile = GST_WFD_RTSP_PROFILE_AVP; + if (g_strrstr (msg->client_rtp_ports->profile, "SAVP")) + *profile = GST_WFD_RTSP_PROFILE_SAVP; + if (g_strrstr (msg->client_rtp_ports->profile, "UDP;unicast")) + *lowertrans = GST_WFD_RTSP_LOWER_TRANS_UDP; + if (g_strrstr (msg->client_rtp_ports->profile, "UDP;multicast")) + *lowertrans = GST_WFD_RTSP_LOWER_TRANS_UDP_MCAST; + if (g_strrstr (msg->client_rtp_ports->profile, "TCP;unicast")) + *lowertrans = GST_WFD_RTSP_LOWER_TRANS_TCP; + if (g_strrstr (msg->client_rtp_ports->profile, "HTTP")) + *lowertrans = GST_WFD_RTSP_LOWER_TRANS_HTTP; + + *rtp_port0 = msg->client_rtp_ports->rtp_port0; + *rtp_port1 = msg->client_rtp_ports->rtp_port1; + + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_presentation_url (GstWFDMessage * msg, gchar * wfd_url0, + gchar * wfd_url1) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (!msg->presentation_url) + msg->presentation_url = g_new0 (GstWFDPresentationUrl, 1); + if (wfd_url0) + msg->presentation_url->wfd_url0 = g_strdup (wfd_url0); + if (wfd_url1) + msg->presentation_url->wfd_url1 = g_strdup (wfd_url1); + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_presentation_url (GstWFDMessage * msg, gchar ** wfd_url0, + gchar ** wfd_url1) +{ + g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); + + if (msg->presentation_url) { + *wfd_url0 = g_strdup (msg->presentation_url->wfd_url0); + *wfd_url1 = g_strdup (msg->presentation_url->wfd_url1); + } + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_set_av_format_change_timing(GstWFDMessage *msg, guint64 PTS, guint64 DTS) +{ + g_return_val_if_fail(msg != NULL, GST_WFD_EINVAL); + + if (!msg->av_format_change_timing) + msg->av_format_change_timing = g_new0(GstWFDAVFormatChangeTiming, 1); + + msg->av_format_change_timing->PTS = PTS; + msg->av_format_change_timing->DTS = DTS; + return GST_WFD_OK; +} + +GstWFDResult +gst_wfd_message_get_av_format_change_timing(GstWFDMessage *msg, guint64 *PTS, guint64 *DTS) +{ + g_return_val_if_fail(msg != NULL, GST_WFD_EINVAL); + g_return_val_if_fail(PTS != NULL, GST_WFD_EINVAL); + g_return_val_if_fail(DTS != NULL, GST_WFD_EINVAL); + + if (msg->av_format_change_timing) { + *PTS = msg->av_format_change_timing->PTS; + *DTS = msg->av_format_change_timing->DTS; + } + + return GST_WFD_OK; +} diff --git a/gst/rtsp-server/gstwfdmessage.h b/gst/rtsp-server/gstwfdmessage.h new file mode 100755 index 0000000..b390ef3 --- /dev/null +++ b/gst/rtsp-server/gstwfdmessage.h @@ -0,0 +1,618 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __GST_WFD_MESSAGE_H__ +#define __GST_WFD_MESSAGE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_STRING_WFD_AUDIO_CODECS "wfd_audio_codecs" +#define GST_STRING_WFD_VIDEO_FORMATS "wfd_video_formats" +#define GST_STRING_WFD_3D_VIDEO_FORMATS "wfd_3d_video_formats" +#define GST_STRING_WFD_CONTENT_PROTECTION "wfd_content_protection" +#define GST_STRING_WFD_DISPLAY_EDID "wfd_display_edid" +#define GST_STRING_WFD_COUPLED_SINK "wfd_coupled_sink" +#define GST_STRING_WFD_TRIGGER_METHOD "wfd_trigger_method" +#define GST_STRING_WFD_PRESENTATION_URL "wfd_presentation_URL" +#define GST_STRING_WFD_CLIENT_RTP_PORTS "wfd_client_rtp_ports" +#define GST_STRING_WFD_ROUTE "wfd_route" +#define GST_STRING_WFD_I2C "wfd_I2C" +#define GST_STRING_WFD_AV_FORMAT_CHANGE_TIMING "wfd_av_format_change_timing" +#define GST_STRING_WFD_PREFERRED_DISPLAY_MODE "wfd_preferred_display_mode" +#define GST_STRING_WFD_STANDBY_RESUME_CAPABILITY "wfd_standby_resume_capability" +#define GST_STRING_WFD_STANDBY "wfd_standby" +#define GST_STRING_WFD_CONNECTOR_TYPE "wfd_connector_type" +#define GST_STRING_WFD_IDR_REQUEST "wfd_idr_request" + +/** + * GstWFDResult: + * @GST_WFD_OK: A successful return value + * @GST_WFD_EINVAL: a function was given invalid parameters + * + * Return values for the WFD functions. + */ +typedef enum { + GST_WFD_OK = 0, + GST_WFD_EINVAL = -1 +} GstWFDResult; + + +typedef enum { + GST_WFD_AUDIO_UNKNOWN = 0, + GST_WFD_AUDIO_LPCM = (1 << 0), + GST_WFD_AUDIO_AAC = (1 << 1), + GST_WFD_AUDIO_AC3 = (1 << 2) +} GstWFDAudioFormats; + +typedef enum { + GST_WFD_FREQ_UNKNOWN = 0, + GST_WFD_FREQ_44100 = (1 << 0), + GST_WFD_FREQ_48000 = (1 << 1) +} GstWFDAudioFreq; + +typedef enum { + GST_WFD_CHANNEL_UNKNOWN = 0, + GST_WFD_CHANNEL_2 = (1 << 0), + GST_WFD_CHANNEL_4 = (1 << 1), + GST_WFD_CHANNEL_6 = (1 << 2), + GST_WFD_CHANNEL_8 = (1 << 3) +} GstWFDAudioChannels; + + +typedef enum { + GST_WFD_VIDEO_UNKNOWN = 0, + GST_WFD_VIDEO_H264 = (1 << 0) +} GstWFDVideoCodecs; + +typedef enum { + GST_WFD_VIDEO_CEA_RESOLUTION = 0, + GST_WFD_VIDEO_VESA_RESOLUTION, + GST_WFD_VIDEO_HH_RESOLUTION +} GstWFDVideoNativeResolution; + +typedef enum { + GST_WFD_CEA_UNKNOWN = 0, + GST_WFD_CEA_640x480P60 = (1 << 0), + GST_WFD_CEA_720x480P60 = (1 << 1), + GST_WFD_CEA_720x480I60 = (1 << 2), + GST_WFD_CEA_720x576P50 = (1 << 3), + GST_WFD_CEA_720x576I50 = (1 << 4), + GST_WFD_CEA_1280x720P30 = (1 << 5), + GST_WFD_CEA_1280x720P60 = (1 << 6), + GST_WFD_CEA_1920x1080P30= (1 << 7), + GST_WFD_CEA_1920x1080P60= (1 << 8), + GST_WFD_CEA_1920x1080I60= (1 << 9), + GST_WFD_CEA_1280x720P25 = (1 << 10), + GST_WFD_CEA_1280x720P50 = (1 << 11), + GST_WFD_CEA_1920x1080P25= (1 << 12), + GST_WFD_CEA_1920x1080P50= (1 << 13), + GST_WFD_CEA_1920x1080I50= (1 << 14), + GST_WFD_CEA_1280x720P24 = (1 << 15), + GST_WFD_CEA_1920x1080P24= (1 << 16) +} GstWFDVideoCEAResolution; + +typedef enum { + GST_WFD_VESA_UNKNOWN = 0, + GST_WFD_VESA_800x600P30 = (1 << 0), + GST_WFD_VESA_800x600P60 = (1 << 1), + GST_WFD_VESA_1024x768P30 = (1 << 2), + GST_WFD_VESA_1024x768P60 = (1 << 3), + GST_WFD_VESA_1152x864P30 = (1 << 4), + GST_WFD_VESA_1152x864P60 = (1 << 5), + GST_WFD_VESA_1280x768P30 = (1 << 6), + GST_WFD_VESA_1280x768P60 = (1 << 7), + GST_WFD_VESA_1280x800P30 = (1 << 8), + GST_WFD_VESA_1280x800P60 = (1 << 9), + GST_WFD_VESA_1360x768P30 = (1 << 10), + GST_WFD_VESA_1360x768P60 = (1 << 11), + GST_WFD_VESA_1366x768P30 = (1 << 12), + GST_WFD_VESA_1366x768P60 = (1 << 13), + GST_WFD_VESA_1280x1024P30 = (1 << 14), + GST_WFD_VESA_1280x1024P60 = (1 << 15), + GST_WFD_VESA_1400x1050P30 = (1 << 16), + GST_WFD_VESA_1400x1050P60 = (1 << 17), + GST_WFD_VESA_1440x900P30 = (1 << 18), + GST_WFD_VESA_1440x900P60 = (1 << 19), + GST_WFD_VESA_1600x900P30 = (1 << 20), + GST_WFD_VESA_1600x900P60 = (1 << 21), + GST_WFD_VESA_1600x1200P30 = (1 << 22), + GST_WFD_VESA_1600x1200P60 = (1 << 23), + GST_WFD_VESA_1680x1024P30 = (1 << 24), + GST_WFD_VESA_1680x1024P60 = (1 << 25), + GST_WFD_VESA_1680x1050P30 = (1 << 26), + GST_WFD_VESA_1680x1050P60 = (1 << 27), + GST_WFD_VESA_1920x1200P30 = (1 << 28), + GST_WFD_VESA_1920x1200P60 = (1 << 29) +} GstWFDVideoVESAResolution; + +typedef enum { + GST_WFD_HH_UNKNOWN = 0, + GST_WFD_HH_800x480P30 = (1 << 0), + GST_WFD_HH_800x480P60 = (1 << 1), + GST_WFD_HH_854x480P30 = (1 << 2), + GST_WFD_HH_854x480P60 = (1 << 3), + GST_WFD_HH_864x480P30 = (1 << 4), + GST_WFD_HH_864x480P60 = (1 << 5), + GST_WFD_HH_640x360P30 = (1 << 6), + GST_WFD_HH_640x360P60 = (1 << 7), + GST_WFD_HH_960x540P30 = (1 << 8), + GST_WFD_HH_960x540P60 = (1 << 9), + GST_WFD_HH_848x480P30 = (1 << 10), + GST_WFD_HH_848x480P60 = (1 << 11) +} GstWFDVideoHHResolution; + +typedef enum { + GST_WFD_H264_UNKNOWN_PROFILE= 0, + GST_WFD_H264_BASE_PROFILE = (1 << 0), + GST_WFD_H264_HIGH_PROFILE = (1 << 1) +} GstWFDVideoH264Profile; + +typedef enum { + GST_WFD_H264_LEVEL_UNKNOWN = 0, + GST_WFD_H264_LEVEL_3_1 = (1 << 0), + GST_WFD_H264_LEVEL_3_2 = (1 << 1), + GST_WFD_H264_LEVEL_4 = (1 << 2), + GST_WFD_H264_LEVEL_4_1 = (1 << 3), + GST_WFD_H264_LEVEL_4_2 = (1 << 4) +} GstWFDVideoH264Level; + +typedef enum { + GST_WFD_HDCP_NONE = 0, + GST_WFD_HDCP_2_0 = (1 << 0), + GST_WFD_HDCP_2_1 = (1 << 1) +} GstWFDHDCPProtection; + +typedef enum { + GST_WFD_SINK_UNKNOWN = -1, + GST_WFD_SINK_NOT_COUPLED = 0, + GST_WFD_SINK_COUPLED, + GST_WFD_SINK_TEARDOWN_COUPLING, + GST_WFD_SINK_RESERVED +} GstWFDCoupledSinkStatus; + +typedef enum { + GST_WFD_TRIGGER_UNKNOWN = 0, + GST_WFD_TRIGGER_SETUP, + GST_WFD_TRIGGER_PAUSE, + GST_WFD_TRIGGER_TEARDOWN, + GST_WFD_TRIGGER_PLAY +} GstWFDTrigger; + +typedef enum { + GST_WFD_RTSP_TRANS_UNKNOWN = 0, + GST_WFD_RTSP_TRANS_RTP = (1 << 0), + GST_WFD_RTSP_TRANS_RDT = (1 << 1) +} GstWFDRTSPTransMode; + +typedef enum { + GST_WFD_RTSP_PROFILE_UNKNOWN = 0, + GST_WFD_RTSP_PROFILE_AVP = (1 << 0), + GST_WFD_RTSP_PROFILE_SAVP = (1 << 1) +} GstWFDRTSPProfile; + +typedef enum { + GST_WFD_RTSP_LOWER_TRANS_UNKNOWN = 0, + GST_WFD_RTSP_LOWER_TRANS_UDP = (1 << 0), + GST_WFD_RTSP_LOWER_TRANS_UDP_MCAST = (1 << 1), + GST_WFD_RTSP_LOWER_TRANS_TCP = (1 << 2), + GST_WFD_RTSP_LOWER_TRANS_HTTP = (1 << 3) +} GstWFDRTSPLowerTrans; + +typedef enum { + GST_WFD_PRIMARY_SINK = 0, + GST_WFD_SECONDARY_SINK +} GstWFDSinkType; + +typedef enum { + GST_WFD_CONNECTOR_VGA = 0, + GST_WFD_CONNECTOR_S, + GST_WFD_CONNECTOR_COMPOSITE, + GST_WFD_CONNECTOR_COMPONENT, + GST_WFD_CONNECTOR_DVI, + GST_WFD_CONNECTOR_HDMI, + GST_WFD_CONNECTOR_LVDS, + GST_WFD_CONNECTOR_RESERVED_7, + GST_WFD_CONNECTOR_JAPANESE_D, + GST_WFD_CONNECTOR_SDI, + GST_WFD_CONNECTOR_DP, + GST_WFD_CONNECTOR_RESERVED_11, + GST_WFD_CONNECTOR_UDI, + GST_WFD_CONNECTOR_NO = 254, + GST_WFD_CONNECTOR_PHYSICAL = 255 +} GstWFDConnector; + + +typedef struct { + gchar *audio_format; + guint32 modes; + guint latency; +} GstWFDAudioCodec; + +typedef struct { + guint count; + GstWFDAudioCodec *list; +} GstWFDAudioCodeclist; + + +typedef struct { + guint CEA_Support; + guint VESA_Support; + guint HH_Support; + guint latency; + guint min_slice_size; + guint slice_enc_params; + guint frame_rate_control_support; +} GstWFDVideoH264MiscParams; + +typedef struct { + guint profile; + guint level; + guint max_hres; + guint max_vres; + GstWFDVideoH264MiscParams misc_params; +} GstWFDVideoH264Codec; + +typedef struct { + guint native; + guint preferred_display_mode_supported; + GstWFDVideoH264Codec H264_codec; +} GstWFDVideoCodec; + +typedef struct { + guint count; + GstWFDVideoCodec *list; +} GstWFDVideoCodeclist; + +typedef struct { + guint video_3d_capability; + guint latency; + guint min_slice_size; + guint slice_enc_params; + guint frame_rate_control_support; +} GstWFD3DVideoH264MiscParams; + +typedef struct { + guint profile; + guint level; + GstWFD3DVideoH264MiscParams misc_params; + guint max_hres; + guint max_vres; +} GstWFD3DVideoH264Codec; + +typedef struct { + guint native; + guint preferred_display_mode_supported; + GstWFD3DVideoH264Codec H264_codec; +} GstWFD3dCapList; + +typedef struct { + guint count; + GstWFD3dCapList *list; +} GstWFD3DFormats; + +typedef struct { + gchar *hdcpversion; + gchar *TCPPort; +} GstWFDHdcp2Spec; + +typedef struct { + GstWFDHdcp2Spec *hdcp2_spec; +} GstWFDContentProtection; + +typedef struct { + guint edid_supported; + guint edid_block_count; + gchar *edid_payload; +} GstWFDDisplayEdid; + + +typedef struct { + guint status; + gchar *sink_address; +} GstWFDCoupled_sink_cap; + +typedef struct { + GstWFDCoupled_sink_cap *coupled_sink_cap; +} GstWFDCoupledSink; + +typedef struct { + gchar *wfd_trigger_method; +} GstWFDTriggerMethod; + +typedef struct { + gchar *wfd_url0; + gchar *wfd_url1; +} GstWFDPresentationUrl; + +typedef struct { + gchar *profile; + guint32 rtp_port0; + guint32 rtp_port1; + gchar *mode; +} GstWFDClientRtpPorts; + +typedef struct { + gchar *destination; +} GstWFDRoute; + +typedef struct { + gboolean I2CPresent; + guint32 I2C_port; +} GstWFDI2C; + +typedef struct { + guint64 PTS; + guint64 DTS; +} GstWFDAVFormatChangeTiming; + +typedef struct { + gboolean displaymodesupported; + guint64 p_clock; + guint32 H; + guint32 HB; + guint32 HSPOL_HSOFF; + guint32 HSW; + guint32 V; + guint32 VB; + guint32 VSPOL_VSOFF; + guint32 VSW; + guint VBS3D; + guint R; + guint V2d_s3d_modes; + guint P_depth; + GstWFDVideoH264Codec H264_codec; +} GstWFDPreferredDisplayMode; + +typedef struct { + gboolean standby_resume_cap; +} GstWFDStandbyResumeCapability; + +typedef struct { + gboolean wfd_standby; +} GstWFDStandby; + +typedef struct { + gboolean supported; + gint32 connector_type; +} GstWFDConnectorType; + +typedef struct { + gboolean idr_request; +} GstWFDIdrRequest; + +/** + * GstWFDMessage: + * @version: the protocol version + * @origin: owner/creator and session identifier + * @session_name: session name + * @information: session information + * @uri: URI of description + * @emails: array of #gchar with email addresses + * @phones: array of #gchar with phone numbers + * @connection: connection information for the session + * @bandwidths: array of #GstWFDBandwidth with bandwidth information + * @times: array of #GstWFDTime with time descriptions + * @zones: array of #GstWFDZone with time zone adjustments + * @key: encryption key + * @attributes: array of #GstWFDAttribute with session attributes + * @medias: array of #GstWFDMedia with media descriptions + * + * The contents of the WFD message. + */ +typedef struct { + GstWFDAudioCodeclist *audio_codecs; + GstWFDVideoCodeclist *video_formats; + GstWFD3DFormats *video_3d_formats; + GstWFDContentProtection *content_protection; + GstWFDDisplayEdid *display_edid; + GstWFDCoupledSink *coupled_sink; + GstWFDTriggerMethod *trigger_method; + GstWFDPresentationUrl *presentation_url; + GstWFDClientRtpPorts *client_rtp_ports; + GstWFDRoute *route; + GstWFDI2C *I2C; + GstWFDAVFormatChangeTiming *av_format_change_timing; + GstWFDPreferredDisplayMode *preferred_display_mode; + GstWFDStandbyResumeCapability *standby_resume_capability; + GstWFDStandby *standby; + GstWFDConnectorType *connector_type; + GstWFDIdrRequest *idr_request; +} GstWFDMessage; + +GType gst_wfd_message_get_type (void); + +#define GST_TYPE_WFD_MESSAGE (gst_wfd_message_get_type()) +#define GST_WFD_MESSAGE_CAST(object) ((GstWFDMessage *)(object)) +#define GST_WFD_MESSAGE(object) (GST_WFD_MESSAGE_CAST(object)) + +/* Session descriptions */ +GstWFDResult gst_wfd_message_new (GstWFDMessage **msg); +GstWFDResult gst_wfd_message_init (GstWFDMessage *msg); +GstWFDResult gst_wfd_message_uninit (GstWFDMessage *msg); +GstWFDResult gst_wfd_message_free (GstWFDMessage *msg); +GstWFDResult gst_wfd_message_copy (const GstWFDMessage *msg, GstWFDMessage **copy); + +GstWFDResult gst_wfd_message_parse_buffer (const guint8 *data, guint size, GstWFDMessage *msg); +gchar* gst_wfd_message_as_text (const GstWFDMessage *msg); +gchar* gst_wfd_message_param_names_as_text (const GstWFDMessage *msg); +GstWFDResult gst_wfd_message_dump (const GstWFDMessage *msg); + + +GstWFDResult gst_wfd_message_set_supported_audio_format(GstWFDMessage *msg, + GstWFDAudioFormats a_codec, + guint a_freq, guint a_channels, + guint a_bitwidth, guint32 a_latency); + +GstWFDResult gst_wfd_message_set_prefered_audio_format(GstWFDMessage *msg, + GstWFDAudioFormats a_codec, + GstWFDAudioFreq a_freq, + GstWFDAudioChannels a_channels, + guint a_bitwidth, guint32 a_latency); + +GstWFDResult gst_wfd_message_get_supported_audio_format (GstWFDMessage *msg, + guint *a_codec, + guint *a_freq, + guint *a_channels, + guint *a_bitwidth, + guint32 *a_latency); + +GstWFDResult gst_wfd_message_get_prefered_audio_format (GstWFDMessage *msg, + GstWFDAudioFormats *a_codec, + GstWFDAudioFreq *a_freq, + GstWFDAudioChannels *a_channels, + guint *a_bitwidth, guint32 *a_latency); + +GstWFDResult gst_wfd_message_set_supported_video_format (GstWFDMessage *msg, + GstWFDVideoCodecs v_codec, + GstWFDVideoNativeResolution v_native, + guint64 v_native_resolution, + guint64 v_cea_resolution, + guint64 v_vesa_resolution, + guint64 v_hh_resolution, + guint v_profile, + guint v_level, + guint32 v_latency, + guint32 v_max_height, + guint32 v_max_width, + guint32 min_slice_size, + guint32 slice_enc_params, + guint frame_rate_control); + +GstWFDResult gst_wfd_message_set_prefered_video_format(GstWFDMessage *msg, + GstWFDVideoCodecs v_codec, + GstWFDVideoNativeResolution v_native, + guint64 v_native_resolution, + GstWFDVideoCEAResolution v_cea_resolution, + GstWFDVideoVESAResolution v_vesa_resolution, + GstWFDVideoHHResolution v_hh_resolution, + GstWFDVideoH264Profile v_profile, + GstWFDVideoH264Level v_level, + guint32 v_latency, + guint32 v_max_height, + guint32 v_max_width, + guint32 min_slice_size, + guint32 slice_enc_params, + guint frame_rate_control); + +GstWFDResult gst_wfd_message_get_supported_video_format(GstWFDMessage *msg, + GstWFDVideoCodecs *v_codec, + GstWFDVideoNativeResolution *v_native, + guint64 *v_native_resolution, + guint64 *v_cea_resolution, + guint64 *v_vesa_resolution, + guint64 *v_hh_resolution, + guint *v_profile, + guint *v_level, + guint32 *v_latency, + guint32 *v_max_height, + guint32 *v_max_width, + guint32 *min_slice_size, + guint32 *slice_enc_params, + guint *frame_rate_control); + +GstWFDResult gst_wfd_message_get_prefered_video_format(GstWFDMessage *msg, + GstWFDVideoCodecs *v_codec, + GstWFDVideoNativeResolution *v_native, + guint64 *v_native_resolution, + GstWFDVideoCEAResolution *v_cea_resolution, + GstWFDVideoVESAResolution *v_vesa_resolution, + GstWFDVideoHHResolution *v_hh_resolution, + GstWFDVideoH264Profile *v_profile, + GstWFDVideoH264Level *v_level, + guint32 *v_latency, + guint32 *v_max_height, + guint32 *v_max_width, + guint32 *min_slice_size, + guint32 *slice_enc_params, + guint *frame_rate_control); + +GstWFDResult gst_wfd_message_set_display_edid (GstWFDMessage *msg, + gboolean edid_supported, + guint32 edid_blockcount, + gchar *edid_playload); + +GstWFDResult gst_wfd_message_get_display_edid (GstWFDMessage *msg, + gboolean *edid_supported, + guint32 *edid_blockcount, + gchar **edid_playload); + +GstWFDResult gst_wfd_message_set_contentprotection_type (GstWFDMessage *msg, + GstWFDHDCPProtection hdcpversion, + guint32 TCPPort); + +GstWFDResult gst_wfd_message_get_contentprotection_type (GstWFDMessage *msg, + GstWFDHDCPProtection *hdcpversion, + guint32 *TCPPort); + +GstWFDResult gst_wfd_messge_set_prefered_rtp_ports (GstWFDMessage *msg, + GstWFDRTSPTransMode trans, + GstWFDRTSPProfile profile, + GstWFDRTSPLowerTrans lowertrans, + guint32 rtp_port0, + guint32 rtp_port1); + +GstWFDResult gst_wfd_message_get_prefered_rtp_ports (GstWFDMessage *msg, + GstWFDRTSPTransMode *trans, + GstWFDRTSPProfile *profile, + GstWFDRTSPLowerTrans *lowertrans, + guint32 *rtp_port0, + guint32 *rtp_port1); + +GstWFDResult gst_wfd_message_set_presentation_url(GstWFDMessage *msg, + gchar *wfd_url0, gchar *wfd_url1); + +GstWFDResult gst_wfd_message_get_presentation_url(GstWFDMessage *msg, gchar **wfd_url0, + gchar **wfd_url1); + +GstWFDResult gst_wfd_message_set_av_format_change_timing(GstWFDMessage *msg, + guint64 PTS, + guint64 DTS); + +GstWFDResult gst_wfd_message_get_av_format_change_timing(GstWFDMessage *msg, + guint64 *PTS, + guint64 *DTS); +G_END_DECLS + +#endif /* __GST_WFD_MESSAGE_H__ */ diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c new file mode 100644 index 0000000..561a722 --- /dev/null +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -0,0 +1,2959 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/** + * SECTION:rtsp-client + * @short_description: A client connection state + * @see_also: #GstRTSPServer, #GstRTSPThreadPool + * + * The client object handles the connection with a client for as long as a TCP + * connection is open. + * + * A #GstRTSPWFDClient is created by #GstRTSPServer when a new connection is + * accepted and it inherits the #GstRTSPMountPoints, #GstRTSPSessionPool, + * #GstRTSPAuth and #GstRTSPThreadPool from the server. + * + * The client connection should be configured with the #GstRTSPConnection using + * gst_rtsp_wfd_client_set_connection() before it can be attached to a #GMainContext + * using gst_rtsp_wfd_client_attach(). From then on the client will handle requests + * on the connection. + * + * Use gst_rtsp_wfd_client_session_filter() to iterate or modify all the + * #GstRTSPSession objects managed by the client object. + * + * Last reviewed on 2013-07-11 (1.0.0) + */ + +#include +#include + +#include "rtsp-client-wfd.h" +#include "rtsp-media-factory-wfd.h" +#include "rtsp-sdp.h" +#include "rtsp-params.h" + +#define GST_RTSP_WFD_CLIENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_WFD_CLIENT, GstRTSPWFDClientPrivate)) + +typedef struct _GstRTSPClientRTPStats GstRTSPClientRTPStats; + +struct _GstRTSPClientRTPStats { + GstRTSPStream *stream; + guint64 last_sent_bytes; + guint64 sent_bytes; + guint last_seqnum; + guint seqnum; + + /* Info in RR (Receiver Report) */ + guint8 fraction_lost; + guint32 cumulative_lost_num; + guint16 max_seqnum; + guint32 arrival_jitter; + guint32 lsr; + guint32 dlsr; + guint32 rtt; + guint resent_packets; +}; + +struct _GstRTSPWFDClientPrivate +{ + GstRTSPWFDClientSendFunc send_func; /* protected by send_lock */ + gpointer send_data; /* protected by send_lock */ + GDestroyNotify send_notify; /* protected by send_lock */ + + /* used to cache the media in the last requested DESCRIBE so that + * we can pick it up in the next SETUP immediately */ + gchar *path; + GstRTSPMedia *media; + + GList *transports; + GList *sessions; + + guint8 m1_done; + guint8 m3_done; + guint8 m4_done; + + /* Host's URL info */ + gchar *host_address; + + /* Parameters for WIFI-DISPLAY */ + guint caCodec; + guint8 audio_codec; + guint cFreq; + guint cChanels; + guint cBitwidth; + guint caLatency; + guint cvCodec; + guint cNative; + guint64 cNativeResolution; + guint64 video_resolution_supported; + gint video_native_resolution; + guint64 cCEAResolution; + guint64 cVESAResolution; + guint64 cHHResolution; + guint cProfile; + guint cLevel; + guint32 cMaxHeight; + guint32 cMaxWidth; + guint32 cFramerate; + guint32 cInterleaved; + guint32 cmin_slice_size; + guint32 cslice_enc_params; + guint cframe_rate_control; + guint cvLatency; + guint ctrans; + guint cprofile; + guint clowertrans; + guint32 crtp_port0; + guint32 crtp_port1; + + gboolean protection_enabled; + GstWFDHDCPProtection hdcp_version; + guint32 hdcp_tcpport; + + gboolean edid_supported; + guint32 edid_hres; + guint32 edid_vres; + + gboolean keep_alive_flag; + GMutex keep_alive_lock; + + /* RTP statistics */ + GstRTSPClientRTPStats stats; + GMutex stats_lock; + guint stats_timer_id; + gboolean rtcp_stats_enabled; +}; + +#define DEFAULT_WFD_TIMEOUT 60 +#define WFD_MOUNT_POINT "/wfd1.0/streamid=0" + +enum +{ + SIGNAL_WFD_OPTIONS_REQUEST, + SIGNAL_WFD_GET_PARAMETER_REQUEST, + SIGNAL_WFD_KEEP_ALIVE_FAIL, + SIGNAL_WFD_LAST +}; + +GST_DEBUG_CATEGORY_STATIC (rtsp_wfd_client_debug); +#define GST_CAT_DEFAULT rtsp_wfd_client_debug + +static guint gst_rtsp_client_wfd_signals[SIGNAL_WFD_LAST] = { 0 }; + +static void gst_rtsp_wfd_client_get_property (GObject * object, guint propid, + GValue * value, GParamSpec * pspec); +static void gst_rtsp_wfd_client_set_property (GObject * object, guint propid, + const GValue * value, GParamSpec * pspec); +static void gst_rtsp_wfd_client_finalize (GObject * obj); + +static gboolean handle_wfd_options_request (GstRTSPClient * client, + GstRTSPContext * ctx); +static gboolean handle_wfd_set_param_request (GstRTSPClient * client, + GstRTSPContext * ctx); +static gboolean handle_wfd_get_param_request (GstRTSPClient * client, + GstRTSPContext * ctx); + +static void send_generic_wfd_response (GstRTSPWFDClient * client, + GstRTSPStatusCode code, GstRTSPContext * ctx); +static gchar *wfd_make_path_from_uri (GstRTSPClient * client, + const GstRTSPUrl * uri); +static void wfd_options_request_done (GstRTSPWFDClient * client, GstRTSPContext *ctx); +static void wfd_get_param_request_done (GstRTSPWFDClient * client, GstRTSPContext *ctx); +static void handle_wfd_response (GstRTSPClient * client, GstRTSPContext * ctx); +static void handle_wfd_play (GstRTSPClient * client, GstRTSPContext * ctx); +static void wfd_set_keep_alive_condition(GstRTSPWFDClient * client); +static gboolean wfd_ckeck_keep_alive_response (gpointer userdata); +static gboolean keep_alive_condition(gpointer userdata); +static gboolean wfd_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media, + GstRTSPStream * stream, GstRTSPContext * ctx); + +GstRTSPResult prepare_trigger_request (GstRTSPWFDClient * client, + GstRTSPMessage * request, GstWFDTriggerType trigger_type, gchar * url); + +GstRTSPResult prepare_request (GstRTSPWFDClient * client, + GstRTSPMessage * request, GstRTSPMethod method, gchar * url); + +void +send_request (GstRTSPWFDClient * client, GstRTSPSession * session, + GstRTSPMessage * request); + +GstRTSPResult +prepare_response (GstRTSPWFDClient * client, GstRTSPMessage * request, + GstRTSPMessage * response, GstRTSPMethod method); + +static GstRTSPResult handle_M1_message (GstRTSPWFDClient * client); +static GstRTSPResult handle_M3_message (GstRTSPWFDClient * client); +static GstRTSPResult handle_M4_message (GstRTSPWFDClient * client); +static GstRTSPResult handle_M16_message (GstRTSPWFDClient * client); + +G_DEFINE_TYPE (GstRTSPWFDClient, gst_rtsp_wfd_client, GST_TYPE_RTSP_CLIENT); + +static void +gst_rtsp_wfd_client_class_init (GstRTSPWFDClientClass * klass) +{ + GObjectClass *gobject_class; + GstRTSPClientClass *rtsp_client_class; + + g_type_class_add_private (klass, sizeof (GstRTSPWFDClientPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + rtsp_client_class = GST_RTSP_CLIENT_CLASS (klass); + + gobject_class->get_property = gst_rtsp_wfd_client_get_property; + gobject_class->set_property = gst_rtsp_wfd_client_set_property; + gobject_class->finalize = gst_rtsp_wfd_client_finalize; + + //klass->create_sdp = create_sdp; + //klass->configure_client_transport = default_configure_client_transport; + //klass->params_set = default_params_set; + //klass->params_get = default_params_get; + + rtsp_client_class->handle_options_request = handle_wfd_options_request; + rtsp_client_class->handle_set_param_request = handle_wfd_set_param_request; + rtsp_client_class->handle_get_param_request = handle_wfd_get_param_request; + rtsp_client_class->make_path_from_uri = wfd_make_path_from_uri; + rtsp_client_class->configure_client_media = wfd_configure_client_media; + + rtsp_client_class->handle_response = handle_wfd_response; + rtsp_client_class->play_request = handle_wfd_play; + + gst_rtsp_client_wfd_signals[SIGNAL_WFD_OPTIONS_REQUEST] = + g_signal_new ("wfd-options-request", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass, + wfd_options_request), NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT); + + gst_rtsp_client_wfd_signals[SIGNAL_WFD_GET_PARAMETER_REQUEST] = + g_signal_new ("wfd-get-parameter-request", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass, + wfd_get_param_request), NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT); + + gst_rtsp_client_wfd_signals[SIGNAL_WFD_KEEP_ALIVE_FAIL] = + g_signal_new ("wfd-keep-alive-fail", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstRTSPWFDClientClass, wfd_keep_alive_fail), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + + klass->wfd_options_request = wfd_options_request_done; + klass->wfd_get_param_request = wfd_get_param_request_done; + + GST_DEBUG_CATEGORY_INIT (rtsp_wfd_client_debug, "rtspwfdclient", 0, + "GstRTSPWFDClient"); +} + +static void +gst_rtsp_wfd_client_init (GstRTSPWFDClient * client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_if_fail (priv != NULL); + + client->priv = priv; + priv->protection_enabled = FALSE; + priv->video_native_resolution = GST_WFD_VIDEO_CEA_RESOLUTION; + priv->video_resolution_supported = GST_WFD_CEA_640x480P60; + priv->audio_codec = GST_WFD_AUDIO_AAC; + priv->keep_alive_flag = FALSE; + + g_mutex_init (&priv->keep_alive_lock); + g_mutex_init (&priv->stats_lock); + + priv->host_address = NULL; + + priv->stats_timer_id = -1; + priv->rtcp_stats_enabled = FALSE; + memset (&priv->stats, 0x00, sizeof (GstRTSPClientRTPStats)); + + GST_INFO_OBJECT (client, "Client is initialized"); +} + +/* A client is finalized when the connection is broken */ +static void +gst_rtsp_wfd_client_finalize (GObject * obj) +{ + GstRTSPWFDClient *client = GST_RTSP_WFD_CLIENT (obj); + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_if_fail (GST_IS_RTSP_WFD_CLIENT (obj)); + g_return_if_fail (priv != NULL); + + GST_INFO ("finalize client %p", client); + + if (priv->host_address) + g_free (priv->host_address); + + if (priv->stats_timer_id > 0) + g_source_remove(priv->stats_timer_id); + + g_mutex_clear (&priv->keep_alive_lock); + g_mutex_clear (&priv->stats_lock); + G_OBJECT_CLASS (gst_rtsp_wfd_client_parent_class)->finalize (obj); +} + +static void +gst_rtsp_wfd_client_get_property (GObject * object, guint propid, + GValue * value, GParamSpec * pspec) +{ + //GstRTSPWFDClient *client = GST_RTSP_WFD_CLIENT (object); + + switch (propid) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static void +gst_rtsp_wfd_client_set_property (GObject * object, guint propid, + const GValue * value, GParamSpec * pspec) +{ + //GstRTSPWFDClient *client = GST_RTSP_WFD_CLIENT (object); + + switch (propid) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +/** + * gst_rtsp_wfd_client_new: + * + * Create a new #GstRTSPWFDClient instance. + * + * Returns: a new #GstRTSPWFDClient + */ +GstRTSPWFDClient * +gst_rtsp_wfd_client_new (void) +{ + GstRTSPWFDClient *result; + + result = g_object_new (GST_TYPE_RTSP_WFD_CLIENT, NULL); + + return result; +} + +void +gst_rtsp_wfd_client_start_wfd (GstRTSPWFDClient * client) +{ + GstRTSPResult res = GST_RTSP_OK; + GST_INFO_OBJECT (client, "gst_rtsp_wfd_client_start_wfd"); + + res = handle_M1_message (client); + if (res < GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "handle_M1_message failed : %d", res); + } + + return; +} + +static gboolean +wfd_display_rtp_stats (gpointer userdata) +{ + guint16 seqnum = 0; + guint64 bytes = 0; + + GstRTSPWFDClient *client = NULL; + GstRTSPWFDClientPrivate *priv = NULL; + + client = (GstRTSPWFDClient *) userdata; + priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + if (!priv) { + GST_ERROR("No priv"); + return FALSE; + } + + g_mutex_lock(&priv->stats_lock); + + seqnum = gst_rtsp_stream_get_current_seqnum (priv->stats.stream); + bytes = gst_rtsp_stream_get_udp_sent_bytes (priv->stats.stream); + + GST_INFO ("----------------------------------------------------\n"); + GST_INFO ("Sent RTP packets : %d", seqnum - priv->stats.last_seqnum); + GST_INFO ("Sent Bytes of RTP packets : %lld bytes", bytes - priv->stats.last_sent_bytes); + + priv->stats.last_seqnum = seqnum; + priv->stats.last_sent_bytes = bytes; + + if (priv->rtcp_stats_enabled) { + GST_INFO ("Fraction Lost: %d", priv->stats.fraction_lost); + GST_INFO ("Cumulative number of packets lost: %d", priv->stats.cumulative_lost_num); + GST_INFO ("Extended highest sequence number received: %d", priv->stats.max_seqnum); + GST_INFO ("Interarrival Jitter: %d", priv->stats.arrival_jitter); + GST_INFO ("Round trip time : %d", priv->stats.rtt); + } + + GST_INFO ("----------------------------------------------------\n"); + + g_mutex_unlock(&priv->stats_lock); + + return TRUE; +} + +static void +on_rtcp_stats (GstRTSPStream *stream, GstStructure *stats, GstRTSPClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + guint fraction_lost, exthighestseq, jitter, lsr, dlsr, rtt; + gint packetslost; + + if (!priv) return; + + g_mutex_lock(&priv->stats_lock); + + gst_structure_get_uint (stats, "rb-fractionlost", &fraction_lost); + gst_structure_get_int (stats, "rb-packetslost", &packetslost); + gst_structure_get_uint (stats, "rb-exthighestseq", &exthighestseq); + gst_structure_get_uint (stats, "rb-jitter", &jitter); + gst_structure_get_uint (stats, "rb-lsr", &lsr); + gst_structure_get_uint (stats, "rb-dlsr", &dlsr); + gst_structure_get_uint (stats, "rb-round-trip", &rtt); + + if (!priv->rtcp_stats_enabled) + priv->rtcp_stats_enabled = TRUE; + + priv->stats.stream = stream; + priv->stats.fraction_lost = (guint8)fraction_lost; + priv->stats.cumulative_lost_num += (guint32)fraction_lost; + priv->stats.max_seqnum = (guint16)exthighestseq; + priv->stats.arrival_jitter = (guint32)jitter; + priv->stats.lsr = (guint32)lsr; + priv->stats.dlsr = (guint32)dlsr; + priv->stats.rtt = (guint32)rtt; + + g_mutex_unlock(&priv->stats_lock); +} + +static gboolean +wfd_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media, + GstRTSPStream * stream, GstRTSPContext * ctx) +{ + if (stream) { + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + if (priv) + priv->stats.stream = stream; + g_signal_connect (stream, "rtcp-statistics", (GCallback) on_rtcp_stats, client); + } + + return GST_RTSP_CLIENT_CLASS (gst_rtsp_wfd_client_parent_class)->configure_client_media (client, media, stream, ctx); +} +static void +wfd_options_request_done (GstRTSPWFDClient * client, GstRTSPContext *ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDClientClass *klass = GST_RTSP_WFD_CLIENT_GET_CLASS (client); + + g_return_if_fail (klass != NULL); + + GST_INFO_OBJECT (client, "M2 done.."); + + res = handle_M3_message (client); + if (res < GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "handle_M3_message failed : %d", res); + } + + if (klass->prepare_resource) { + klass->prepare_resource (client, ctx); + } + + return; +} + +static void +wfd_get_param_request_done (GstRTSPWFDClient * client, GstRTSPContext *ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + GstRTSPWFDClientClass *klass = GST_RTSP_WFD_CLIENT_GET_CLASS (client); + + g_return_if_fail (priv != NULL && klass != NULL); + + priv->m3_done = TRUE; + GST_INFO_OBJECT (client, "M3 done.."); + + res = handle_M4_message (client); + if (res < GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "handle_M4_message failed : %d", res); + } + + if (klass->confirm_resource) { + klass->confirm_resource (client, ctx); + } + + return; +} + +static guint +wfd_get_prefered_audio_codec (guint8 srcAudioCodec, + guint sinkAudioCodec) +{ + int i = 0; + guint codec = 0; + for (i = 0; i < 8; i++) { + if (((sinkAudioCodec << i) & 0x80) + && ((srcAudioCodec << i) & 0x80)) { + codec = (0x01 << (7 - i)); + break; + } + } + return codec; +} + +static guint64 +wfd_get_prefered_resolution (guint64 srcResolution, + guint64 sinkResolution, + GstWFDVideoNativeResolution native, + guint32 * cMaxWidth, + guint32 * cMaxHeight, guint32 * cFramerate, guint32 * interleaved) +{ + int i = 0; + guint64 resolution = 0; + for (i = 0; i < 32; i++) { + if (((sinkResolution << i) & 0x80000000) + && ((srcResolution << i) & 0x80000000)) { + resolution = ((guint64) 0x00000001 << (31 - i)); + break; + } + } + switch (native) { + case GST_WFD_VIDEO_CEA_RESOLUTION: + { + switch (resolution) { + case GST_WFD_CEA_640x480P60: + *cMaxWidth = 640; + *cMaxHeight = 480; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_CEA_720x480P60: + *cMaxWidth = 720; + *cMaxHeight = 480; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_CEA_720x480I60: + *cMaxWidth = 720; + *cMaxHeight = 480; + *cFramerate = 60; + *interleaved = 1; + break; + case GST_WFD_CEA_720x576P50: + *cMaxWidth = 720; + *cMaxHeight = 576; + *cFramerate = 50; + *interleaved = 0; + break; + case GST_WFD_CEA_720x576I50: + *cMaxWidth = 720; + *cMaxHeight = 576; + *cFramerate = 50; + *interleaved = 1; + break; + case GST_WFD_CEA_1280x720P30: + *cMaxWidth = 1280; + *cMaxHeight = 720; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_CEA_1280x720P60: + *cMaxWidth = 1280; + *cMaxHeight = 720; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080P30: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080P60: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080I60: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 60; + *interleaved = 1; + break; + case GST_WFD_CEA_1280x720P25: + *cMaxWidth = 1280; + *cMaxHeight = 720; + *cFramerate = 25; + *interleaved = 0; + break; + case GST_WFD_CEA_1280x720P50: + *cMaxWidth = 1280; + *cMaxHeight = 720; + *cFramerate = 50; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080P25: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 25; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080P50: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 50; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080I50: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 50; + *interleaved = 1; + break; + case GST_WFD_CEA_1280x720P24: + *cMaxWidth = 1280; + *cMaxHeight = 720; + *cFramerate = 24; + *interleaved = 0; + break; + case GST_WFD_CEA_1920x1080P24: + *cMaxWidth = 1920; + *cMaxHeight = 1080; + *cFramerate = 24; + *interleaved = 0; + break; + default: + *cMaxWidth = 0; + *cMaxHeight = 0; + *cFramerate = 0; + *interleaved = 0; + break; + } + } + break; + case GST_WFD_VIDEO_VESA_RESOLUTION: + { + switch (resolution) { + case GST_WFD_VESA_800x600P30: + *cMaxWidth = 800; + *cMaxHeight = 600; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_800x600P60: + *cMaxWidth = 800; + *cMaxHeight = 600; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1024x768P30: + *cMaxWidth = 1024; + *cMaxHeight = 768; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1024x768P60: + *cMaxWidth = 1024; + *cMaxHeight = 768; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1152x864P30: + *cMaxWidth = 1152; + *cMaxHeight = 864; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1152x864P60: + *cMaxWidth = 1152; + *cMaxHeight = 864; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1280x768P30: + *cMaxWidth = 1280; + *cMaxHeight = 768; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1280x768P60: + *cMaxWidth = 1280; + *cMaxHeight = 768; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1280x800P30: + *cMaxWidth = 1280; + *cMaxHeight = 800; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1280x800P60: + *cMaxWidth = 1280; + *cMaxHeight = 800; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1360x768P30: + *cMaxWidth = 1360; + *cMaxHeight = 768; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1360x768P60: + *cMaxWidth = 1360; + *cMaxHeight = 768; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1366x768P30: + *cMaxWidth = 1366; + *cMaxHeight = 768; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1366x768P60: + *cMaxWidth = 1366; + *cMaxHeight = 768; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1280x1024P30: + *cMaxWidth = 1280; + *cMaxHeight = 1024; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1280x1024P60: + *cMaxWidth = 1280; + *cMaxHeight = 1024; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1400x1050P30: + *cMaxWidth = 1400; + *cMaxHeight = 1050; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1400x1050P60: + *cMaxWidth = 1400; + *cMaxHeight = 1050; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1440x900P30: + *cMaxWidth = 1440; + *cMaxHeight = 900; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1440x900P60: + *cMaxWidth = 1440; + *cMaxHeight = 900; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1600x900P30: + *cMaxWidth = 1600; + *cMaxHeight = 900; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1600x900P60: + *cMaxWidth = 1600; + *cMaxHeight = 900; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1600x1200P30: + *cMaxWidth = 1600; + *cMaxHeight = 1200; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1600x1200P60: + *cMaxWidth = 1600; + *cMaxHeight = 1200; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1680x1024P30: + *cMaxWidth = 1680; + *cMaxHeight = 1024; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1680x1024P60: + *cMaxWidth = 1680; + *cMaxHeight = 1024; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1680x1050P30: + *cMaxWidth = 1680; + *cMaxHeight = 1050; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1680x1050P60: + *cMaxWidth = 1680; + *cMaxHeight = 1050; + *cFramerate = 60; + *interleaved = 0; + break; + case GST_WFD_VESA_1920x1200P30: + *cMaxWidth = 1920; + *cMaxHeight = 1200; + *cFramerate = 30; + *interleaved = 0; + break; + case GST_WFD_VESA_1920x1200P60: + *cMaxWidth = 1920; + *cMaxHeight = 1200; + *cFramerate = 60; + *interleaved = 0; + break; + default: + *cMaxWidth = 0; + *cMaxHeight = 0; + *cFramerate = 0; + *interleaved = 0; + break; + } + } + break; + case GST_WFD_VIDEO_HH_RESOLUTION: + { + *interleaved = 0; + switch (resolution) { + case GST_WFD_HH_800x480P30: + *cMaxWidth = 800; + *cMaxHeight = 480; + *cFramerate = 30; + break; + case GST_WFD_HH_800x480P60: + *cMaxWidth = 800; + *cMaxHeight = 480; + *cFramerate = 60; + break; + case GST_WFD_HH_854x480P30: + *cMaxWidth = 854; + *cMaxHeight = 480; + *cFramerate = 30; + break; + case GST_WFD_HH_854x480P60: + *cMaxWidth = 854; + *cMaxHeight = 480; + *cFramerate = 60; + break; + case GST_WFD_HH_864x480P30: + *cMaxWidth = 864; + *cMaxHeight = 480; + *cFramerate = 30; + break; + case GST_WFD_HH_864x480P60: + *cMaxWidth = 864; + *cMaxHeight = 480; + *cFramerate = 60; + break; + case GST_WFD_HH_640x360P30: + *cMaxWidth = 640; + *cMaxHeight = 360; + *cFramerate = 30; + break; + case GST_WFD_HH_640x360P60: + *cMaxWidth = 640; + *cMaxHeight = 360; + *cFramerate = 60; + break; + case GST_WFD_HH_960x540P30: + *cMaxWidth = 960; + *cMaxHeight = 540; + *cFramerate = 30; + break; + case GST_WFD_HH_960x540P60: + *cMaxWidth = 960; + *cMaxHeight = 540; + *cFramerate = 60; + break; + case GST_WFD_HH_848x480P30: + *cMaxWidth = 848; + *cMaxHeight = 480; + *cFramerate = 30; + break; + case GST_WFD_HH_848x480P60: + *cMaxWidth = 848; + *cMaxHeight = 480; + *cFramerate = 60; + break; + default: + *cMaxWidth = 0; + *cMaxHeight = 0; + *cFramerate = 0; + *interleaved = 0; + break; + } + } + break; + + default: + *cMaxWidth = 0; + *cMaxHeight = 0; + *cFramerate = 0; + *interleaved = 0; + break; + } + return resolution; +} + +static gchar * +wfd_make_path_from_uri (GstRTSPClient * client, const GstRTSPUrl * uri) +{ + gchar *path; + + GST_DEBUG_OBJECT (client, "Got URI host : %s", uri->host); + GST_DEBUG_OBJECT (client, "Got URI abspath : %s", uri->abspath); + + path = g_strdup ("/wfd1.0/streamid=0"); + + return path; +} + +static void +handle_wfd_play (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPWFDClient *_client = GST_RTSP_WFD_CLIENT (client); + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_if_fail (priv != NULL); + + wfd_set_keep_alive_condition(_client); + + priv->stats_timer_id = g_timeout_add (2000, wfd_display_rtp_stats, _client); +} + +static void +handle_wfd_response (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + guint8 *data = NULL; + guint size = 0; + + GstRTSPWFDClient *_client = GST_RTSP_WFD_CLIENT (client); + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_if_fail (priv != NULL); + + GST_INFO_OBJECT (_client, "Handling response.."); + + if (!ctx) { + GST_ERROR_OBJECT (_client, "Context is NULL"); + goto error; + } + + if (!ctx->response) { + GST_ERROR_OBJECT (_client, "Response is NULL"); + goto error; + } + + /* parsing the GET_PARAMTER response */ + res = gst_rtsp_message_get_body (ctx->response, (guint8 **) & data, &size); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (_client, "Failed to get body of response..."); + return; + } + + GST_INFO_OBJECT (_client, "Response body is %d", size); + if (size > 0) { + if (!priv->m3_done) { + GstWFDResult wfd_res; + GstWFDMessage *msg = NULL; + /* Parse M3 response from sink */ + wfd_res = gst_wfd_message_new (&msg); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to create wfd message..."); + goto error; + } + + wfd_res = gst_wfd_message_init (msg); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to init wfd message..."); + goto error; + } + + wfd_res = gst_wfd_message_parse_buffer (data, size, msg); + + GST_DEBUG_OBJECT (client, "M3 response server side message body: %s", + gst_wfd_message_as_text (msg)); + + /* Get the audio formats supported by WFDSink */ + if (msg->audio_codecs) { + wfd_res = + gst_wfd_message_get_supported_audio_format (msg, &priv->caCodec, + &priv->cFreq, &priv->cChanels, &priv->cBitwidth, &priv->caLatency); + if (wfd_res != GST_WFD_OK) { + GST_WARNING_OBJECT (client, + "Failed to get wfd support audio formats..."); + goto error; + } + } + + /* Get the Video formats supported by WFDSink */ + wfd_res = + gst_wfd_message_get_supported_video_format (msg, &priv->cvCodec, + &priv->cNative, &priv->cNativeResolution, + (guint64 *) & priv->cCEAResolution, + (guint64 *) & priv->cVESAResolution, + (guint64 *) & priv->cHHResolution, &priv->cProfile, &priv->cLevel, + &priv->cvLatency, &priv->cMaxHeight, &priv->cMaxWidth, + &priv->cmin_slice_size, &priv->cslice_enc_params, + &priv->cframe_rate_control); + if (wfd_res != GST_WFD_OK) { + GST_WARNING_OBJECT (client, + "Failed to get wfd supported video formats..."); + goto error; + } + + if (msg->client_rtp_ports) { + /* Get the RTP ports preferred by WFDSink */ + wfd_res = + gst_wfd_message_get_prefered_rtp_ports (msg, &priv->ctrans, + &priv->cprofile, &priv->clowertrans, &priv->crtp_port0, + &priv->crtp_port1); + if (wfd_res != GST_WFD_OK) { + GST_WARNING_OBJECT (client, + "Failed to get wfd prefered RTP ports..."); + goto error; + } + } + + if (msg->display_edid) { + guint32 edid_block_count = 0; + gchar *edid_payload = NULL; + priv->edid_supported = FALSE; + /* Get the display edid preferred by WFDSink */ + GST_DEBUG_OBJECT (client, "Going to gst_wfd_message_get_display_edid"); + wfd_res = + gst_wfd_message_get_display_edid (msg, &priv->edid_supported, + &edid_block_count, &edid_payload); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to get wfd display edid..."); + goto error; + } + GST_DEBUG_OBJECT (client, " edid supported: %d edid_block_count: %d", + priv->edid_supported, edid_block_count); + if (priv->edid_supported) { + priv->edid_hres = 0; + priv->edid_vres = 0; + priv->edid_hres = + (guint32) (((edid_payload[54 + 4] >> 4) << 8) | edid_payload[54 + + 2]); + priv->edid_vres = + (guint32) (((edid_payload[54 + 7] >> 4) << 8) | edid_payload[54 + + 5]); + GST_DEBUG_OBJECT (client, " edid supported Hres: %d Wres: %d", + priv->edid_hres, priv->edid_vres); + if ((priv->edid_hres < 640) || (priv->edid_vres < 480) + || (priv->edid_hres > 1920) || (priv->edid_vres > 1080)) { + priv->edid_hres = 0; + priv->edid_vres = 0; + priv->edid_supported = FALSE; + GST_WARNING_OBJECT (client, " edid invalid resolutions"); + } + } + } + + if (msg->content_protection) { +#if 0 + /*Get the hdcp version and tcp port by WFDSink */ + wfd_res = + gst_wfd_message_get_contentprotection_type (msg, + &priv->hdcp_version, &priv->hdcp_tcpport); + GST_DEBUG ("hdcp version =%d, tcp port = %d", priv->hdcp_version, + priv->hdcp_tcpport); + if (priv->hdcp_version > 0 && priv->hdcp_tcpport > 0) + priv->protection_enabled = TRUE; + + if (wfd_res != GST_WFD_OK) { + GST_WARNING_OBJECT (client, + "Failed to get wfd content protection..."); + goto error; + } +#else + GST_WARNING_OBJECT (client, "Don't use content protection"); +#endif + } + + g_signal_emit (_client, + gst_rtsp_client_wfd_signals[SIGNAL_WFD_GET_PARAMETER_REQUEST], 0, + ctx); + } else { + /* TODO-WFD: Handle another GET_PARAMETER response with body */ + } + } else if (size == 0) { + if (!priv->m1_done) { + GST_INFO_OBJECT (_client, "M1 response is done"); + priv->m1_done = TRUE; + } else if (!priv->m4_done) { + GST_INFO_OBJECT (_client, "M4 response is done"); + priv->m4_done = TRUE; + + gst_rtsp_wfd_client_trigger_request (_client, WFD_TRIGGER_SETUP); + } else { + g_mutex_lock(&priv->keep_alive_lock); + if (priv->keep_alive_flag == FALSE) { + GST_INFO_OBJECT (_client, "M16 response is done"); + priv->keep_alive_flag = TRUE; + } + g_mutex_unlock(&priv->keep_alive_lock); + } + } + + return; + +error: + return; +} + +static gboolean +handle_wfd_options_request (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMethod options; + gchar *tmp = NULL; + gchar *str = NULL; + gchar *user_agent = NULL; + + GstRTSPWFDClient *_client = GST_RTSP_WFD_CLIENT (client); + + options = GST_RTSP_OPTIONS | + GST_RTSP_PAUSE | + GST_RTSP_PLAY | + GST_RTSP_SETUP | + GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN; + + str = gst_rtsp_options_as_text (options); + + /*append WFD specific method */ + tmp = g_strdup (", org.wfa.wfd1.0"); + g_strlcat (str, tmp, strlen (tmp) + strlen (str) + 1); + + gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK, + gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request); + + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PUBLIC, str); + g_free (str); + str = NULL; + + res = + gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_USER_AGENT, + &user_agent, 0); + if (res == GST_RTSP_OK) { + gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_USER_AGENT, + user_agent); + } else { + return FALSE; + } + + res = gst_rtsp_client_send_message (client, NULL, ctx->response); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "gst_rtsp_client_send_message failed : %d", res); + return FALSE; + } + + GST_DEBUG_OBJECT (client, "Sent M2 response..."); + + g_signal_emit (_client, + gst_rtsp_client_wfd_signals[SIGNAL_WFD_OPTIONS_REQUEST], 0, ctx); + + return TRUE; +} + +static gboolean +handle_wfd_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + guint8 *data = NULL; + guint size = 0; + + GstRTSPWFDClient *_client = GST_RTSP_WFD_CLIENT (client); + + /* parsing the GET_PARAMTER request */ + res = gst_rtsp_message_get_body (ctx->request, (guint8 **) & data, &size); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (_client, "Failed to get body of request..."); + return FALSE; + } + + if (size == 0) { + send_generic_wfd_response (_client, GST_RTSP_STS_OK, ctx); + } else { + /* TODO-WFD: Handle other GET_PARAMETER request from sink */ + } + + return TRUE; +} + +static gboolean +handle_wfd_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + guint8 *data = NULL; + guint size = 0; + + GstRTSPWFDClient *_client = GST_RTSP_WFD_CLIENT (client); + + res = gst_rtsp_message_get_body (ctx->request, &data, &size); + if (res != GST_RTSP_OK) + goto bad_request; + + if (size == 0) { + /* no body, keep-alive request */ + send_generic_wfd_response (_client, GST_RTSP_STS_OK, ctx); + } else { + if (data != NULL) { + GST_INFO_OBJECT (_client, "SET_PARAMETER Request : %s(%d)", data, size); + if (g_strcmp0 ((const gchar *) data, "wfd_idr_request")) + send_generic_wfd_response (_client, GST_RTSP_STS_OK, ctx); +#if 0 + else + /* TODO-WFD : Handle other set param request */ + send_generic_wfd_response (_client, GST_RTSP_STS_OK, ctx); +#endif + } else { + goto bad_request; + } + } + + return TRUE; + + /* ERRORS */ +bad_request: + { + GST_ERROR ("_client %p: bad request", _client); + send_generic_wfd_response (_client, GST_RTSP_STS_BAD_REQUEST, ctx); + return FALSE; + } +} + +#if 0 +static gboolean +gst_rtsp_wfd_client_parse_methods (GstRTSPWFDClient * client, + GstRTSPMessage * response) +{ + GstRTSPHeaderField field; + gchar *respoptions; + gchar **options; + gint indx = 0; + gint i; + gboolean found_wfd_method = FALSE; + + /* reset supported methods */ + client->supported_methods = 0; + + /* Try Allow Header first */ + field = GST_RTSP_HDR_ALLOW; + while (TRUE) { + respoptions = NULL; + gst_rtsp_message_get_header (response, field, &respoptions, indx); + if (indx == 0 && !respoptions) { + /* if no Allow header was found then try the Public header... */ + field = GST_RTSP_HDR_PUBLIC; + gst_rtsp_message_get_header (response, field, &respoptions, indx); + } + if (!respoptions) + break; + + /* If we get here, the server gave a list of supported methods, parse + * them here. The string is like: + * + * OPTIONS, PLAY, SETUP, ... + */ + options = g_strsplit (respoptions, ",", 0); + + for (i = 0; options[i]; i++) { + gchar *stripped; + gint method; + + stripped = g_strstrip (options[i]); + method = gst_rtsp_find_method (stripped); + + if (!g_ascii_strcasecmp ("org.wfa.wfd1.0", stripped)) + found_wfd_method = TRUE; + + /* keep bitfield of supported methods */ + if (method != GST_RTSP_INVALID) + client->supported_methods |= method; + } + g_strfreev (options); + + indx++; + } + + if (!found_wfd_method) { + GST_ERROR_OBJECT (client, + "WFD client is not supporting WFD mandatory message : org.wfa.wfd1.0..."); + goto no_required_methods; + } + + /* Checking mandatory method */ + if (!(client->supported_methods & GST_RTSP_SET_PARAMETER)) { + GST_ERROR_OBJECT (client, + "WFD client is not supporting WFD mandatory message : SET_PARAMETER..."); + goto no_required_methods; + } + + /* Checking mandatory method */ + if (!(client->supported_methods & GST_RTSP_GET_PARAMETER)) { + GST_ERROR_OBJECT (client, + "WFD client is not supporting WFD mandatory message : GET_PARAMETER..."); + goto no_required_methods; + } + + if (!(client->supported_methods & GST_RTSP_OPTIONS)) { + GST_INFO_OBJECT (client, "assuming OPTIONS is supported by client..."); + client->supported_methods |= GST_RTSP_OPTIONS; + } + + return TRUE; + +/* ERRORS */ +no_required_methods: + { + GST_ELEMENT_ERROR (client, RESOURCE, OPEN_READ, (NULL), + ("WFD Client does not support mandatory methods.")); + return FALSE; + } +} +#endif + +typedef enum +{ + M1_REQ_MSG, + M1_RES_MSG, + M2_REQ_MSG, + M2_RES_MSG, + M3_REQ_MSG, + M3_RES_MSG, + M4_REQ_MSG, + M4_RES_MSG, + M5_REQ_MSG, + TEARDOWN_TRIGGER, + PLAY_TRIGGER, + PAUSE_TRIGGER, +} GstWFDMessageType; + +static gboolean +_set_negotiated_audio_codec (GstRTSPWFDClient *client, + guint audio_codec) +{ + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + + GstRTSPMediaFactory *factory = NULL; + GstRTSPMountPoints *mount_points = NULL; + gchar *path = NULL; + gint matched = 0; + gboolean ret = TRUE; + + if (!(mount_points = gst_rtsp_client_get_mount_points (parent_client))) { + ret = FALSE; + GST_ERROR_OBJECT (client, "Failed to set negotiated audio codec: no mount points..."); + goto no_mount_points; + } + + path = g_strdup(WFD_MOUNT_POINT); + if (!path) { + ret = FALSE; + GST_ERROR_OBJECT (client, "Failed to set negotiated audio codec: no path..."); + goto no_path; + } + + if (!(factory = gst_rtsp_mount_points_match (mount_points, + path, &matched))) { + GST_ERROR_OBJECT (client, "Failed to set negotiated audio codec: no factory..."); + ret = FALSE; + goto no_factory; + } + + gst_rtsp_media_factory_wfd_set_audio_codec (factory, + audio_codec); + ret = TRUE; + + g_object_unref(factory); + +no_factory: + g_free(path); +no_path: + g_object_unref(mount_points); +no_mount_points: + return ret; +} + +static gboolean +_set_negotiated_resolution(GstRTSPWFDClient *client, + guint32 width, guint32 height) +{ + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + + GstRTSPMediaFactory *factory = NULL; + GstRTSPMountPoints *mount_points = NULL; + gchar *path = NULL; + gint matched = 0; + gboolean ret = TRUE; + + if (!(mount_points = gst_rtsp_client_get_mount_points (parent_client))) { + ret = FALSE; + GST_ERROR_OBJECT (client, "Failed to set negotiated resolution: no mount points..."); + goto no_mount_points; + } + + path = g_strdup(WFD_MOUNT_POINT); + if (!path) { + ret = FALSE; + GST_ERROR_OBJECT (client, "Failed to set negotiated resolution: no path..."); + goto no_path; + } + + if (!(factory = gst_rtsp_mount_points_match (mount_points, + path, &matched))) { + GST_ERROR_OBJECT (client, "Failed to set negotiated resolution: no factory..."); + ret = FALSE; + goto no_factory; + } + + gst_rtsp_media_factory_wfd_set_negotiated_resolution(factory, + width, height); + ret = TRUE; + + g_object_unref(factory); + +no_factory: + g_free(path); +no_path: + g_object_unref(mount_points); +no_mount_points: + return ret; +} + +static void +_set_wfd_message_body (GstRTSPWFDClient * client, GstWFDMessageType msg_type, + gchar ** data, guint * len) +{ + GString *buf = NULL; + GstWFDMessage *msg = NULL; + GstWFDResult wfd_res = GST_WFD_EINVAL; + GstRTSPWFDClientPrivate *priv = NULL; + priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_if_fail (priv != NULL); + + buf = g_string_new (""); + g_return_if_fail (buf != NULL); + + if (msg_type == M3_REQ_MSG) { + /* create M3 request to be sent */ + wfd_res = gst_wfd_message_new (&msg); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to create wfd message..."); + goto error; + } + + wfd_res = gst_wfd_message_init (msg); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to init wfd message..."); + goto error; + } + + /* set the supported audio formats by the WFD server */ + wfd_res = + gst_wfd_message_set_supported_audio_format (msg, GST_WFD_AUDIO_UNKNOWN, + GST_WFD_FREQ_UNKNOWN, GST_WFD_CHANNEL_UNKNOWN, 0, 0); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, + "Failed to set supported audio formats on wfd message..."); + goto error; + } + + /* set the supported Video formats by the WFD server */ + wfd_res = + gst_wfd_message_set_supported_video_format (msg, GST_WFD_VIDEO_UNKNOWN, + GST_WFD_VIDEO_CEA_RESOLUTION, GST_WFD_CEA_UNKNOWN, GST_WFD_CEA_UNKNOWN, + GST_WFD_VESA_UNKNOWN, GST_WFD_HH_UNKNOWN, GST_WFD_H264_UNKNOWN_PROFILE, + GST_WFD_H264_LEVEL_UNKNOWN, 0, 0, 0, 0, 0, 0); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, + "Failed to set supported video formats on wfd message..."); + goto error; + } + + wfd_res = gst_wfd_message_set_display_edid (msg, 0, 0, NULL); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, + "Failed to set display edid type on wfd message..."); + goto error; + } + + if (priv->protection_enabled) { + wfd_res = + gst_wfd_message_set_contentprotection_type (msg, GST_WFD_HDCP_NONE, + 0); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, + "Failed to set supported content protection type on wfd message..."); + goto error; + } + } + + /* set the preffered RTP ports for the WFD server */ + wfd_res = + gst_wfd_messge_set_prefered_rtp_ports (msg, GST_WFD_RTSP_TRANS_UNKNOWN, + GST_WFD_RTSP_PROFILE_UNKNOWN, GST_WFD_RTSP_LOWER_TRANS_UNKNOWN, 0, 0); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, + "Failed to set supported video formats on wfd message..."); + goto error; + } + + *data = gst_wfd_message_param_names_as_text (msg); + if (*data == NULL) { + GST_ERROR_OBJECT (client, "Failed to get wfd message as text..."); + goto error; + } else { + *len = strlen (*data); + } + } else if (msg_type == M4_REQ_MSG) { + GstRTSPUrl *url = NULL; + + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + GstRTSPConnection *connection = + gst_rtsp_client_get_connection (parent_client); + + /* Parameters for the preffered audio formats */ + GstWFDAudioFormats taudiocodec = GST_WFD_AUDIO_UNKNOWN; + GstWFDAudioFreq taudiofreq = GST_WFD_FREQ_UNKNOWN; + GstWFDAudioChannels taudiochannels = GST_WFD_CHANNEL_UNKNOWN; + + /* Parameters for the preffered video formats */ + GstWFDVideoCEAResolution tcCEAResolution = GST_WFD_CEA_UNKNOWN; + GstWFDVideoVESAResolution tcVESAResolution = GST_WFD_VESA_UNKNOWN; + GstWFDVideoHHResolution tcHHResolution = GST_WFD_HH_UNKNOWN; + GstWFDVideoH264Profile tcProfile; + GstWFDVideoH264Level tcLevel; + guint64 resolution_supported = 0; + + url = gst_rtsp_connection_get_url (connection); + if (url == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + return; + } + + /* Logic to negotiate with information of M3 response */ + /* create M4 request to be sent */ + wfd_res = gst_wfd_message_new (&msg); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to create wfd message..."); + goto error; + } + + wfd_res = gst_wfd_message_init (msg); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to init wfd message..."); + goto error; + } + + g_string_append_printf (buf, "rtsp://"); + + if (priv->host_address) { + g_string_append (buf, priv->host_address); + } else { + GST_ERROR_OBJECT (client, "Failed to get host address"); + if (buf) g_string_free (buf, TRUE); + goto error; + } + + g_string_append_printf (buf, "/wfd1.0/streamid=0"); + wfd_res = + gst_wfd_message_set_presentation_url (msg, g_string_free (buf, FALSE), + NULL); + + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to set presentation url"); + goto error; + } + + taudiocodec = wfd_get_prefered_audio_codec (priv->audio_codec, priv->caCodec); + priv->caCodec = taudiocodec; + if (!_set_negotiated_audio_codec(client, priv->caCodec)) { + GST_ERROR_OBJECT (client, "Failed to set negotiated " + "audio codec to media factory..."); + } + + if (priv->cFreq & GST_WFD_FREQ_48000) + taudiofreq = GST_WFD_FREQ_48000; + else if (priv->cFreq & GST_WFD_FREQ_44100) + taudiofreq = GST_WFD_FREQ_44100; + priv->cFreq = taudiofreq; + + /* TODO-WFD: Currently only 2 channels is present */ + if (priv->cChanels & GST_WFD_CHANNEL_8) + taudiochannels = GST_WFD_CHANNEL_2; + else if (priv->cChanels & GST_WFD_CHANNEL_6) + taudiochannels = GST_WFD_CHANNEL_2; + else if (priv->cChanels & GST_WFD_CHANNEL_4) + taudiochannels = GST_WFD_CHANNEL_2; + else if (priv->cChanels & GST_WFD_CHANNEL_2) + taudiochannels = GST_WFD_CHANNEL_2; + priv->cChanels = taudiochannels; + + wfd_res = + gst_wfd_message_set_prefered_audio_format (msg, taudiocodec, taudiofreq, + taudiochannels, priv->cBitwidth, priv->caLatency); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (priv, "Failed to set preffered audio formats..."); + goto error; + } + + /* Set the preffered video formats */ + priv->cvCodec = GST_WFD_VIDEO_H264; + priv->cProfile = tcProfile = GST_WFD_H264_BASE_PROFILE; + priv->cLevel = tcLevel = GST_WFD_H264_LEVEL_3_1; + + resolution_supported = priv->video_resolution_supported; + + /* TODO-WFD: Need to verify this logic + if(priv->edid_supported) { + if (priv->edid_hres < 1920) resolution_supported = resolution_supported & 0x8C7F; + if (priv->edid_hres < 1280) resolution_supported = resolution_supported & 0x1F; + if (priv->edid_hres < 720) resolution_supported = resolution_supported & 0x01; + } + */ + + if (priv->video_native_resolution == GST_WFD_VIDEO_CEA_RESOLUTION) { + tcCEAResolution = + wfd_get_prefered_resolution (resolution_supported, + priv->cCEAResolution, priv->video_native_resolution, &priv->cMaxWidth, + &priv->cMaxHeight, &priv->cFramerate, &priv->cInterleaved); + GST_DEBUG + ("wfd negotiated resolution: %08x, width: %d, height: %d, framerate: %d, interleaved: %d", + tcCEAResolution, priv->cMaxWidth, priv->cMaxHeight, priv->cFramerate, + priv->cInterleaved); + } else if (priv->video_native_resolution == GST_WFD_VIDEO_VESA_RESOLUTION) { + tcVESAResolution = + wfd_get_prefered_resolution (resolution_supported, + priv->cVESAResolution, priv->video_native_resolution, + &priv->cMaxWidth, &priv->cMaxHeight, &priv->cFramerate, + &priv->cInterleaved); + GST_DEBUG + ("wfd negotiated resolution: %08x, width: %d, height: %d, framerate: %d, interleaved: %d", + tcVESAResolution, priv->cMaxWidth, priv->cMaxHeight, priv->cFramerate, + priv->cInterleaved); + } else if (priv->video_native_resolution == GST_WFD_VIDEO_HH_RESOLUTION) { + tcHHResolution = + wfd_get_prefered_resolution (resolution_supported, + priv->cHHResolution, priv->video_native_resolution, &priv->cMaxWidth, + &priv->cMaxHeight, &priv->cFramerate, &priv->cInterleaved); + GST_DEBUG + ("wfd negotiated resolution: %08x, width: %d, height: %d, framerate: %d, interleaved: %d", + tcHHResolution, priv->cMaxWidth, priv->cMaxHeight, priv->cFramerate, + priv->cInterleaved); + } + + if (!_set_negotiated_resolution(client, priv->cMaxWidth, + priv->cMaxHeight)) { + GST_ERROR_OBJECT (client, "Failed to set negotiated " + "resolution to media factory..."); + } + + wfd_res = + gst_wfd_message_set_prefered_video_format (msg, priv->cvCodec, + priv->video_native_resolution, GST_WFD_CEA_UNKNOWN, tcCEAResolution, + tcVESAResolution, tcHHResolution, tcProfile, tcLevel, priv->cvLatency, + priv->cMaxWidth, priv->cMaxHeight, priv->cmin_slice_size, + priv->cslice_enc_params, priv->cframe_rate_control); + + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, "Failed to set preffered video formats..."); + goto error; + } + + /* set the preffered RTP ports for the WFD server */ + wfd_res = + gst_wfd_messge_set_prefered_rtp_ports (msg, GST_WFD_RTSP_TRANS_RTP, + GST_WFD_RTSP_PROFILE_AVP, GST_WFD_RTSP_LOWER_TRANS_UDP, priv->crtp_port0, priv->crtp_port1); + if (wfd_res != GST_WFD_OK) { + GST_ERROR_OBJECT (client, + "Failed to set supported video formats on wfd message..."); + goto error; + } + + *data = gst_wfd_message_as_text (msg); + if (*data == NULL) { + GST_ERROR_OBJECT (client, "Failed to get wfd message as text..."); + goto error; + } else { + *len = strlen (*data); + } + } else if (msg_type == M5_REQ_MSG) { + g_string_append (buf, "wfd_trigger_method: SETUP"); + g_string_append (buf, "\r\n"); + *len = buf->len; + *data = g_string_free (buf, FALSE); + } else if (msg_type == TEARDOWN_TRIGGER) { + g_string_append (buf, "wfd_trigger_method: TEARDOWN"); + g_string_append (buf, "\r\n"); + *len = buf->len; + *data = g_string_free (buf, FALSE); + } else if (msg_type == PLAY_TRIGGER) { + g_string_append (buf, "wfd_trigger_method: PLAY"); + g_string_append (buf, "\r\n"); + *len = buf->len; + *data = g_string_free (buf, FALSE); + } else if (msg_type == PAUSE_TRIGGER) { + g_string_append (buf, "wfd_trigger_method: PAUSE"); + g_string_append (buf, "\r\n"); + *len = buf->len; + *data = g_string_free (buf, FALSE); + } else { + return; + } + + return; + +error: + *data = NULL; + *len = 0; + + return; +} + +/** +* prepare_request: +* @client: client object +* @request : requst message to be prepared +* @method : RTSP method of the request +* @url : url need to be in the request +* @message_type : WFD message type +* @trigger_type : trigger method to be used for M5 mainly +* +* Prepares request based on @method & @message_type +* +* Returns: a #GstRTSPResult. +*/ +GstRTSPResult +prepare_request (GstRTSPWFDClient * client, GstRTSPMessage * request, + GstRTSPMethod method, gchar * url) +{ + GstRTSPResult res = GST_RTSP_OK; + gchar *str = NULL; + + if (method == GST_RTSP_GET_PARAMETER || method == GST_RTSP_SET_PARAMETER) { + g_free (url); + url = g_strdup ("rtsp://localhost/wfd1.0"); + } + + GST_DEBUG_OBJECT (client, "Preparing request: %d", method); + + /* initialize the request */ + res = gst_rtsp_message_init_request (request, method, url); + if (res < 0) { + GST_ERROR ("init request failed"); + return res; + } + + switch (method) { + /* Prepare OPTIONS request to send */ + case GST_RTSP_OPTIONS:{ + /* add wfd specific require filed "org.wfa.wfd1.0" */ + str = g_strdup ("org.wfa.wfd1.0"); + res = gst_rtsp_message_add_header (request, GST_RTSP_HDR_REQUIRE, str); + if (res < 0) { + GST_ERROR ("Failed to add header"); + g_free (str); + return res; + } + + g_free (str); + break; + } + + /* Prepare GET_PARAMETER request */ + case GST_RTSP_GET_PARAMETER:{ + gchar *msg = NULL; + guint msglen = 0; + GString *msglength; + + /* add content type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE, + "text/parameters"); + if (res < 0) { + GST_ERROR ("Failed to add header"); + return res; + } + + _set_wfd_message_body (client, M3_REQ_MSG, &msg, &msglen); + msglength = g_string_new (""); + g_string_append_printf (msglength, "%d", msglen); + GST_DEBUG ("M3 server side message body: %s", msg); + + /* add content-length type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_LENGTH, + g_string_free (msglength, FALSE)); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + g_free (msg); + break; + } + + /* Prepare SET_PARAMETER request */ + case GST_RTSP_SET_PARAMETER:{ + gchar *msg = NULL; + guint msglen = 0; + GString *msglength; + + /* add content type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE, + "text/parameters"); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp request..."); + goto error; + } + + _set_wfd_message_body (client, M4_REQ_MSG, &msg, &msglen); + msglength = g_string_new (""); + g_string_append_printf (msglength, "%d", msglen); + GST_DEBUG ("M4 server side message body: %s", msg); + + /* add content-length type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_LENGTH, + g_string_free (msglength, FALSE)); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + g_free (msg); + break; + } + + default:{ + } + } + + return res; + +error: + return GST_RTSP_ERROR; +} + +GstRTSPResult +prepare_trigger_request (GstRTSPWFDClient * client, GstRTSPMessage * request, + GstWFDTriggerType trigger_type, gchar * url) +{ + GstRTSPResult res = GST_RTSP_OK; + + /* initialize the request */ + res = gst_rtsp_message_init_request (request, GST_RTSP_SET_PARAMETER, url); + if (res < 0) { + GST_ERROR ("init request failed"); + return res; + } + + switch (trigger_type) { + case WFD_TRIGGER_SETUP:{ + gchar *msg; + guint msglen = 0; + GString *msglength; + + /* add content type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE, + "text/parameters"); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp request..."); + goto error; + } + + _set_wfd_message_body (client, M5_REQ_MSG, &msg, &msglen); + msglength = g_string_new (""); + g_string_append_printf (msglength, "%d", msglen); + GST_DEBUG ("M5 server side message body: %s", msg); + + /* add content-length type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_LENGTH, + g_string_free (msglength, FALSE)); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + g_free (msg); + break; + } + case WFD_TRIGGER_TEARDOWN:{ + gchar *msg; + guint msglen = 0; + GString *msglength; + + /* add content type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE, + "text/parameters"); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp request..."); + goto error; + } + + _set_wfd_message_body (client, TEARDOWN_TRIGGER, &msg, &msglen); + msglength = g_string_new (""); + g_string_append_printf (msglength, "%d", msglen); + GST_DEBUG ("Trigger TEARDOWN server side message body: %s", msg); + + /* add content-length type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_LENGTH, + g_string_free (msglength, FALSE)); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + g_free (msg); + break; + } + case WFD_TRIGGER_PLAY:{ + gchar *msg; + guint msglen = 0; + GString *msglength; + + /* add content type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE, + "text/parameters"); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp request..."); + goto error; + } + + _set_wfd_message_body (client, PLAY_TRIGGER, &msg, &msglen); + msglength = g_string_new (""); + g_string_append_printf (msglength, "%d", msglen); + GST_DEBUG ("Trigger PLAY server side message body: %s", msg); + + /* add content-length type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_LENGTH, + g_string_free (msglength, FALSE)); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + g_free (msg); + break; + } + case WFD_TRIGGER_PAUSE:{ + gchar *msg; + guint msglen = 0; + GString *msglength; + + /* add content type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE, + "text/parameters"); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp request..."); + goto error; + } + + _set_wfd_message_body (client, PAUSE_TRIGGER, &msg, &msglen); + msglength = g_string_new (""); + g_string_append_printf (msglength, "%d", msglen); + GST_DEBUG ("Trigger PAUSE server side message body: %s", msg); + + /* add content-length type */ + res = + gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_LENGTH, + g_string_free (msglength, FALSE)); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "Failed to add header to rtsp message..."); + goto error; + } + + g_free (msg); + break; + } + /* TODO-WFD: implement to handle other trigger type */ + default:{ + } + } + + return res; + +error: + return res; +} + + +void +send_request (GstRTSPWFDClient * client, GstRTSPSession * session, + GstRTSPMessage * request) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + + /* remove any previous header */ + gst_rtsp_message_remove_header (request, GST_RTSP_HDR_SESSION, -1); + + /* add the new session header for new session ids */ + if (session) { + guint timeout; + const gchar *sessionid = NULL; + gchar *str; + + sessionid = gst_rtsp_session_get_sessionid (session); + GST_INFO_OBJECT (client, "Session id : %s", sessionid); + + timeout = gst_rtsp_session_get_timeout (session); + if (timeout != DEFAULT_WFD_TIMEOUT) + str = g_strdup_printf ("%s; timeout=%d", sessionid, timeout); + else + str = g_strdup (sessionid); + + gst_rtsp_message_take_header (request, GST_RTSP_HDR_SESSION, str); + } +#if 0 + if (gst_debug_category_get_threshold (rtsp_wfd_client_debug) >= GST_LEVEL_LOG) { + gst_rtsp_message_dump (request); + } +#endif + res = gst_rtsp_client_send_message (parent_client, session, request); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "gst_rtsp_client_send_message failed : %d", res); + } + + gst_rtsp_message_unset (request); +} + +/** +* prepare_response: +* @client: client object +* @request : requst message received +* @response : response to be prepare based on request +* @method : RTSP method +* +* prepare response to the request based on @method & @message_type +* +* Returns: a #GstRTSPResult. +*/ +GstRTSPResult +prepare_response (GstRTSPWFDClient * client, GstRTSPMessage * request, + GstRTSPMessage * response, GstRTSPMethod method) +{ + GstRTSPResult res = GST_RTSP_OK; + + switch (method) { + /* prepare OPTIONS response */ + case GST_RTSP_OPTIONS:{ + GstRTSPMethod options; + gchar *tmp = NULL; + gchar *str = NULL; + gchar *user_agent = NULL; + + options = GST_RTSP_OPTIONS | + GST_RTSP_PAUSE | + GST_RTSP_PLAY | + GST_RTSP_SETUP | + GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN; + + str = gst_rtsp_options_as_text (options); + + /*append WFD specific method */ + tmp = g_strdup (", org.wfa.wfd1.0"); + g_strlcat (str, tmp, strlen (tmp) + strlen (str) + 1); + + gst_rtsp_message_init_response (response, GST_RTSP_STS_OK, + gst_rtsp_status_as_text (GST_RTSP_STS_OK), request); + + gst_rtsp_message_add_header (response, GST_RTSP_HDR_PUBLIC, str); + g_free (str); + str = NULL; + res = + gst_rtsp_message_get_header (request, GST_RTSP_HDR_USER_AGENT, + &user_agent, 0); + if (res == GST_RTSP_OK) { + gst_rtsp_message_add_header (response, GST_RTSP_HDR_USER_AGENT, + user_agent); + } else + res = GST_RTSP_OK; + break; + } + default: + GST_ERROR_OBJECT (client, "Unhandled method..."); + return GST_RTSP_EINVAL; + break; + } + + return res; +} + +static void +send_generic_wfd_response (GstRTSPWFDClient * client, GstRTSPStatusCode code, + GstRTSPContext * ctx) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + + gst_rtsp_message_init_response (ctx->response, code, + gst_rtsp_status_as_text (code), ctx->request); + + res = gst_rtsp_client_send_message (parent_client, NULL, ctx->response); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (client, "gst_rtsp_client_send_message failed : %d", res); + } +} + + +static GstRTSPResult +handle_M1_message (GstRTSPWFDClient * client) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMessage request = { 0 }; + + res = prepare_request (client, &request, GST_RTSP_OPTIONS, (gchar *) "*"); + if (GST_RTSP_OK != res) { + GST_ERROR_OBJECT (client, "Failed to prepare M1 request....\n"); + return res; + } + + GST_DEBUG_OBJECT (client, "Sending M1 request.. (OPTIONS request)"); + + send_request (client, NULL, &request); + + return res; +} + +/** +* handle_M3_message: +* @client: client object +* +* Handles M3 WFD message. +* This API will send M3 message (GET_PARAMETER) to WFDSink to query supported formats by the WFDSink. +* After getting supported formats info, this API will set those values on WFDConfigMessage obj +* +* Returns: a #GstRTSPResult. +*/ +static GstRTSPResult +handle_M3_message (GstRTSPWFDClient * client) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMessage request = { 0 }; + GstRTSPUrl *url = NULL; + gchar *url_str = NULL; + + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + GstRTSPConnection *connection = + gst_rtsp_client_get_connection (parent_client); + + url = gst_rtsp_connection_get_url (connection); + if (url == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + res = GST_RTSP_ERROR; + goto error; + } + + url_str = gst_rtsp_url_get_request_uri (url); + if (url_str == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + res = GST_RTSP_ERROR; + goto error; + } + + res = prepare_request (client, &request, GST_RTSP_GET_PARAMETER, url_str); + if (GST_RTSP_OK != res) { + GST_ERROR_OBJECT (client, "Failed to prepare M3 request....\n"); + goto error; + } + + GST_DEBUG_OBJECT (client, "Sending GET_PARAMETER request message (M3)..."); + + send_request (client, NULL, &request); + + return res; + +error: + return res; +} + +static GstRTSPResult +handle_M4_message (GstRTSPWFDClient * client) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMessage request = { 0 }; + GstRTSPUrl *url = NULL; + gchar *url_str = NULL; + + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + GstRTSPConnection *connection = + gst_rtsp_client_get_connection (parent_client); + + url = gst_rtsp_connection_get_url (connection); + if (url == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + res = GST_RTSP_ERROR; + goto error; + } + + url_str = gst_rtsp_url_get_request_uri (url); + if (url_str == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + res = GST_RTSP_ERROR; + goto error; + } + + res = prepare_request (client, &request, GST_RTSP_SET_PARAMETER, url_str); + if (GST_RTSP_OK != res) { + GST_ERROR_OBJECT (client, "Failed to prepare M4 request....\n"); + goto error; + } + + GST_DEBUG_OBJECT (client, "Sending SET_PARAMETER request message (M4)..."); + + send_request (client, NULL, &request); + + return res; + +error: + return res; +} + +GstRTSPResult +gst_rtsp_wfd_client_trigger_request (GstRTSPWFDClient * client, + GstWFDTriggerType type) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMessage request = { 0 }; + GstRTSPUrl *url = NULL; + gchar *url_str = NULL; + + GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client); + GstRTSPConnection *connection = + gst_rtsp_client_get_connection (parent_client); + + url = gst_rtsp_connection_get_url (connection); + if (url == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + res = GST_RTSP_ERROR; + goto error; + } + + url_str = gst_rtsp_url_get_request_uri (url); + if (url_str == NULL) { + GST_ERROR_OBJECT (client, "Failed to get connection URL"); + res = GST_RTSP_ERROR; + goto error; + } + + res = prepare_trigger_request (client, &request, type, url_str); + if (GST_RTSP_OK != res) { + GST_ERROR_OBJECT (client, "Failed to prepare M5 request....\n"); + goto error; + } + + GST_DEBUG_OBJECT (client, "Sending trigger request message...: %d", type); + + send_request (client, NULL, &request); + + return res; + +error: + return res; +} + +GstRTSPResult +gst_rtsp_wfd_client_set_video_supported_resolution (GstRTSPWFDClient * client, + guint64 supported_reso) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_val_if_fail (priv != NULL, GST_RTSP_EINVAL); + + priv->video_resolution_supported = supported_reso; + GST_DEBUG ("Resolution : %"G_GUINT64_FORMAT, supported_reso); + + return res; +} + +GstRTSPResult +gst_rtsp_wfd_client_set_video_native_resolution (GstRTSPWFDClient * client, + guint64 native_reso) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_val_if_fail (priv != NULL, GST_RTSP_EINVAL); + + priv->video_native_resolution = native_reso; + GST_DEBUG ("Native Resolution : %"G_GUINT64_FORMAT, native_reso); + + return res; +} + +GstRTSPResult +gst_rtsp_wfd_client_set_audio_codec (GstRTSPWFDClient * client, + guint8 audio_codec) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_val_if_fail (priv != NULL, GST_RTSP_EINVAL); + + priv->audio_codec = audio_codec; + GST_DEBUG ("Audio codec : %d", audio_codec); + + return res; +} + +static gboolean +wfd_ckeck_keep_alive_response (gpointer userdata) +{ + GstRTSPWFDClient *client = (GstRTSPWFDClient *)userdata; + GstRTSPWFDClientPrivate *priv = NULL; + if (!client) { + return FALSE; + } + + priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, GST_RTSP_EINVAL); + + if (priv->keep_alive_flag) { + return FALSE; + } + else { + GST_INFO ("%p: source error notification", client); + + g_signal_emit (client, + gst_rtsp_client_wfd_signals[SIGNAL_WFD_KEEP_ALIVE_FAIL], 0, + NULL); + return FALSE; + } +} + +/*Sending keep_alive (M16) message. + Without calling prepare_request function.*/ +static GstRTSPResult +handle_M16_message (GstRTSPWFDClient * client) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPMessage request = { 0 }; + gchar *url_str = NULL; + + url_str = g_strdup("rtsp://localhost/wfd1.0"); + + res = gst_rtsp_message_init_request (&request, GST_RTSP_GET_PARAMETER, url_str); + if (res < 0) { + GST_ERROR ("init request failed"); + return FALSE; + } + + send_request (client, NULL, &request); + return GST_RTSP_OK; +} + +/*CHecking whether source has got response of any request. + * If yes, keep alive message is sent otherwise error message + * will be displayed.*/ +static gboolean +keep_alive_condition(gpointer userdata) +{ + GstRTSPWFDClient *client; + GstRTSPWFDClientPrivate *priv; + GstRTSPResult res; + client = (GstRTSPWFDClient *)userdata; + if (!client) { + return FALSE; + } + priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_val_if_fail (priv != NULL, FALSE); + + g_mutex_lock(&priv->keep_alive_lock); + if(!priv->keep_alive_flag) { + g_timeout_add(5000, wfd_ckeck_keep_alive_response, client); + } + else { + GST_DEBUG_OBJECT (client, "have received last keep alive message response"); + } + + GST_DEBUG("sending keep alive message"); + res = handle_M16_message(client); + if(res == GST_RTSP_OK) { + priv->keep_alive_flag = FALSE; + } else { + GST_ERROR_OBJECT (client, "Failed to send Keep Alive Message"); + g_mutex_unlock(&priv->keep_alive_lock); + return FALSE; + } + + g_mutex_unlock(&priv->keep_alive_lock); + return TRUE; +} + +static +void wfd_set_keep_alive_condition(GstRTSPWFDClient * client) +{ + g_timeout_add((DEFAULT_WFD_TIMEOUT-5)*1000, keep_alive_condition, client); +} + +void +gst_rtsp_wfd_client_set_host_address (GstRTSPWFDClient *client, const gchar * address) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + + g_return_if_fail (priv != NULL); + + if (priv->host_address) { + g_free (priv->host_address); + } + + priv->host_address = g_strdup (address); +} + +guint +gst_rtsp_wfd_client_get_audio_codec(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->caCodec; +} + +guint +gst_rtsp_wfd_client_get_audio_freq(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cFreq; +} + +guint +gst_rtsp_wfd_client_get_audio_channels(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cChanels; +} + +guint +gst_rtsp_wfd_client_get_audio_bit_width(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cBitwidth; +} + +guint +gst_rtsp_wfd_client_get_audio_latency(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->caLatency; +} + +guint +gst_rtsp_wfd_client_get_video_codec(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cvCodec; +} + +guint +gst_rtsp_wfd_client_get_video_native(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cNative; +} + +guint64 +gst_rtsp_wfd_client_get_video_native_resolution(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cNativeResolution; +} + +guint64 +gst_rtsp_wfd_client_get_video_cea_resolution(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cCEAResolution; +} + +guint64 +gst_rtsp_wfd_client_get_video_vesa_resolution(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cVESAResolution; +} + +guint64 +gst_rtsp_wfd_client_get_video_hh_resolution(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cHHResolution; +} + +guint +gst_rtsp_wfd_client_get_video_profile(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cProfile; +} + +guint +gst_rtsp_wfd_client_get_video_level(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cLevel; +} + +guint +gst_rtsp_wfd_client_get_video_latency(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cvLatency; +} + +guint32 +gst_rtsp_wfd_client_get_video_max_height(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cMaxHeight; +} + +guint32 +gst_rtsp_wfd_client_get_video_max_width(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cMaxWidth; +} + +guint32 +gst_rtsp_wfd_client_get_video_framerate(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cFramerate; +} + +guint32 +gst_rtsp_wfd_client_get_video_min_slice_size(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cmin_slice_size; +} + +guint32 +gst_rtsp_wfd_client_get_video_slice_enc_params(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cslice_enc_params; +} + +guint +gst_rtsp_wfd_client_get_video_framerate_control(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->cframe_rate_control; +} + +guint32 +gst_rtsp_wfd_client_get_rtp_port0(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->crtp_port0; +} + +guint32 +gst_rtsp_wfd_client_get_rtp_port1(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->crtp_port1; +} + +gboolean +gst_rtsp_wfd_client_get_edid_supported(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->edid_supported; +} + +guint32 +gst_rtsp_wfd_client_get_edid_hresolution(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->edid_hres; +} + +guint32 +gst_rtsp_wfd_client_get_edid_vresolution(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->edid_vres; +} + +gboolean +gst_rtsp_wfd_client_get_protection_enabled(GstRTSPWFDClient *client) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + return priv->protection_enabled; +} + +void +gst_rtsp_wfd_client_set_audio_freq(GstRTSPWFDClient *client, guint freq) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + priv->cFreq = freq; +} + +void +gst_rtsp_wfd_client_set_edid_supported(GstRTSPWFDClient *client, gboolean supported) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + priv->edid_supported = supported; +} + +void +gst_rtsp_wfd_client_set_edid_hresolution(GstRTSPWFDClient *client, guint32 reso) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + priv->edid_hres = reso; +} + +void +gst_rtsp_wfd_client_set_edid_vresolution(GstRTSPWFDClient *client, guint32 reso) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + priv->edid_vres = reso; +} + +void +gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean enable) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + priv->protection_enabled = enable; +} + +void gst_rtsp_wfd_client_set_keep_alive_flag(GstRTSPWFDClient *client, gboolean flag) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_val_if_fail (priv != NULL, 0); + + g_mutex_lock(&priv->keep_alive_lock); + if (priv->keep_alive_flag == !(flag)) + priv->keep_alive_flag = flag; + g_mutex_unlock(&priv->keep_alive_lock); +} + +void +gst_rtsp_wfd_client_set_aud_codec (GstRTSPWFDClient *client, guint acodec) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->caCodec = acodec; +} + +void +gst_rtsp_wfd_client_set_audio_channels(GstRTSPWFDClient *client, guint channels) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cChanels = channels; +} + +void +gst_rtsp_wfd_client_set_audio_bit_width(GstRTSPWFDClient *client, guint bwidth) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cBitwidth = bwidth; +} + +void +gst_rtsp_wfd_client_set_audio_latency(GstRTSPWFDClient *client, guint latency) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->caLatency = latency; +} + +void +gst_rtsp_wfd_client_set_video_codec(GstRTSPWFDClient *client, guint vcodec) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cvCodec = vcodec; +} + +void +gst_rtsp_wfd_client_set_video_native(GstRTSPWFDClient *client, guint native) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cNative = native; +} + +void +gst_rtsp_wfd_client_set_vid_native_resolution(GstRTSPWFDClient *client, guint64 res) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cNativeResolution = res; +} + +void +gst_rtsp_wfd_client_set_video_cea_resolution(GstRTSPWFDClient *client, guint64 res) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cCEAResolution = res; +} + +void +gst_rtsp_wfd_client_set_video_vesa_resolution(GstRTSPWFDClient *client, guint64 res) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cVESAResolution = res; +} + +void +gst_rtsp_wfd_client_set_video_hh_resolution(GstRTSPWFDClient *client, guint64 res) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cHHResolution = res; +} + +void +gst_rtsp_wfd_client_set_video_profile(GstRTSPWFDClient *client, guint profile) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cProfile = profile; +} + +void +gst_rtsp_wfd_client_set_video_level(GstRTSPWFDClient *client, guint level) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cLevel = level; +} + +void +gst_rtsp_wfd_client_set_video_latency(GstRTSPWFDClient *client, guint latency) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cvLatency = latency; +} + +void +gst_rtsp_wfd_client_set_video_max_height(GstRTSPWFDClient *client, guint32 height) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cMaxHeight = height; +} + +void +gst_rtsp_wfd_client_set_video_max_width(GstRTSPWFDClient *client, guint32 width) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cMaxWidth = width; +} + +void +gst_rtsp_wfd_client_set_video_framerate(GstRTSPWFDClient *client, guint32 framerate) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cFramerate = framerate; +} + +void +gst_rtsp_wfd_client_set_video_min_slice_size(GstRTSPWFDClient *client, guint32 slice_size) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cmin_slice_size = slice_size; +} + +void +gst_rtsp_wfd_client_set_video_slice_enc_params(GstRTSPWFDClient *client, guint32 enc_params) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cslice_enc_params = enc_params; +} + +void +gst_rtsp_wfd_client_set_video_framerate_control(GstRTSPWFDClient *client, guint framerate) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->cframe_rate_control = framerate; +} + +void +gst_rtsp_wfd_client_set_rtp_port0(GstRTSPWFDClient *client, guint32 port) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->crtp_port0 = port; +} + +void +gst_rtsp_wfd_client_set_rtp_port1(GstRTSPWFDClient *client, guint32 port) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->crtp_port1 = port; +} diff --git a/gst/rtsp-server/rtsp-client-wfd.h b/gst/rtsp-server/rtsp-client-wfd.h new file mode 100644 index 0000000..baf54b9 --- /dev/null +++ b/gst/rtsp-server/rtsp-client-wfd.h @@ -0,0 +1,221 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#ifndef __GST_RTSP_WFD_CLIENT_H__ +#define __GST_RTSP_WFD_CLIENT_H__ + +G_BEGIN_DECLS + +typedef struct _GstRTSPWFDClient GstRTSPWFDClient; +typedef struct _GstRTSPWFDClientClass GstRTSPWFDClientClass; +typedef struct _GstRTSPWFDClientPrivate GstRTSPWFDClientPrivate; + +#include "rtsp-context.h" +#include "rtsp-mount-points.h" +#include "rtsp-sdp.h" +#include "rtsp-auth.h" +#include "rtsp-client.h" +#include "gstwfdmessage.h" + +#define GST_TYPE_RTSP_WFD_CLIENT (gst_rtsp_wfd_client_get_type ()) +#define GST_IS_RTSP_WFD_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_WFD_CLIENT)) +#define GST_IS_RTSP_WFD_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_WFD_CLIENT)) +#define GST_RTSP_WFD_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_WFD_CLIENT, GstRTSPWFDClientClass)) +#define GST_RTSP_WFD_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_WFD_CLIENT, GstRTSPWFDClient)) +#define GST_RTSP_WFD_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_WFD_CLIENT, GstRTSPWFDClientClass)) +#define GST_RTSP_WFD_CLIENT_CAST(obj) ((GstRTSPWFDClient*)(obj)) +#define GST_RTSP_WFD_CLIENT_CLASS_CAST(klass) ((GstRTSPWFDClientClass*)(klass)) + + +/** + * + */ +typedef enum { + WFD_TRIGGER_SETUP, + WFD_TRIGGER_PAUSE, + WFD_TRIGGER_TEARDOWN, + WFD_TRIGGER_PLAY +} GstWFDTriggerType; + +/** + * GstRTSPWFDClientSendFunc: + * @client: a #GstRTSPWFDClient + * @message: a #GstRTSPMessage + * @close: close the connection + * @user_data: user data when registering the callback + * + * This callback is called when @client wants to send @message. When @close is + * %TRUE, the connection should be closed when the message has been sent. + * + * Returns: %TRUE on success. + */ +typedef gboolean (*GstRTSPWFDClientSendFunc) (GstRTSPWFDClient *client, + GstRTSPMessage *message, + gboolean close, + gpointer user_data); + +/** + * GstRTSPWFDClient: + * + * The client object represents the connection and its state with a client. + */ +struct _GstRTSPWFDClient { + GstRTSPClient parent; + + gint supported_methods; + /*< private >*/ + GstRTSPWFDClientPrivate *priv; + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstRTSPWFDClientClass: + * @create_sdp: called when the SDP needs to be created for media. + * @configure_client_media: called when the stream in media needs to be configured. + * The default implementation will configure the blocksize on the payloader when + * spcified in the request headers. + * @configure_client_transport: called when the client transport needs to be + * configured. + * @params_set: set parameters. This function should also initialize the + * RTSP response(ctx->response) via a call to gst_rtsp_message_init_response() + * @params_get: get parameters. This function should also initialize the + * RTSP response(ctx->response) via a call to gst_rtsp_message_init_response() + * + * The client class structure. + */ +struct _GstRTSPWFDClientClass { + GstRTSPClientClass parent_class; + + GstRTSPResult (*prepare_resource) (GstRTSPWFDClient *client, GstRTSPContext *ctx); + GstRTSPResult (*confirm_resource) (GstRTSPWFDClient *client, GstRTSPContext *ctx); + + /* signals */ + void (*wfd_options_request) (GstRTSPWFDClient *client, GstRTSPContext *ctx); + void (*wfd_get_param_request) (GstRTSPWFDClient *client, GstRTSPContext *ctx); + void (*wfd_keep_alive_fail) (GstRTSPWFDClient *client); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + +GType gst_rtsp_wfd_client_get_type (void); + +GstRTSPWFDClient * gst_rtsp_wfd_client_new (void); + +void gst_rtsp_wfd_client_set_host_address ( + GstRTSPWFDClient *client, const gchar * address); + +void gst_rtsp_wfd_client_start_wfd(GstRTSPWFDClient *client); +GstRTSPResult gst_rtsp_wfd_client_trigger_request ( + GstRTSPWFDClient * client, GstWFDTriggerType type); + +GstRTSPResult gst_rtsp_wfd_client_set_video_supported_resolution ( + GstRTSPWFDClient * client, guint64 supported_reso); +GstRTSPResult gst_rtsp_wfd_client_set_video_native_resolution ( + GstRTSPWFDClient * client, guint64 native_reso); +GstRTSPResult gst_rtsp_wfd_client_set_audio_codec ( + GstRTSPWFDClient * client, guint8 audio_codec); + +guint gst_rtsp_wfd_client_get_audio_codec(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_audio_freq(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_audio_channels(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_audio_bit_width(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_audio_latency(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_video_codec(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_video_native(GstRTSPWFDClient *client); +guint64 gst_rtsp_wfd_client_get_video_native_resolution(GstRTSPWFDClient *client); +guint64 gst_rtsp_wfd_client_get_video_cea_resolution(GstRTSPWFDClient *client); +guint64 gst_rtsp_wfd_client_get_video_vesa_resolution(GstRTSPWFDClient *client); +guint64 gst_rtsp_wfd_client_get_video_hh_resolution(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_video_profile(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_video_level(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_video_latency(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_video_max_height(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_video_max_width(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_video_framerate(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_video_min_slice_size(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_video_slice_enc_params(GstRTSPWFDClient *client); +guint gst_rtsp_wfd_client_get_video_framerate_control(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_rtp_port0(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_rtp_port1(GstRTSPWFDClient *client); +gboolean gst_rtsp_wfd_client_get_edid_supported(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_edid_hresolution(GstRTSPWFDClient *client); +guint32 gst_rtsp_wfd_client_get_edid_vresolution(GstRTSPWFDClient *client); +gboolean gst_rtsp_wfd_client_get_protection_enabled(GstRTSPWFDClient *client); + +void gst_rtsp_wfd_client_set_audio_freq(GstRTSPWFDClient *client, guint freq); +void gst_rtsp_wfd_client_set_edid_supported(GstRTSPWFDClient *client, gboolean supported); +void gst_rtsp_wfd_client_set_edid_hresolution(GstRTSPWFDClient *client, guint32 reso); +void gst_rtsp_wfd_client_set_edid_vresolution(GstRTSPWFDClient *client, guint32 reso); +void gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean enable); +void gst_rtsp_wfd_client_set_keep_alive_flag(GstRTSPWFDClient *client, gboolean flag); +void gst_rtsp_wfd_client_set_aud_codec(GstRTSPWFDClient *client, guint acodec); +void gst_rtsp_wfd_client_set_audio_channels(GstRTSPWFDClient *client, guint channels); +void gst_rtsp_wfd_client_set_audio_bit_width(GstRTSPWFDClient *client, guint bwidth); +void gst_rtsp_wfd_client_set_audio_latency(GstRTSPWFDClient *client, guint latency); +void gst_rtsp_wfd_client_set_video_codec(GstRTSPWFDClient *client, guint vcodec); +void gst_rtsp_wfd_client_set_video_native(GstRTSPWFDClient *client, guint native); +void gst_rtsp_wfd_client_set_vid_native_resolution(GstRTSPWFDClient *client, guint64 res); +void gst_rtsp_wfd_client_set_video_cea_resolution(GstRTSPWFDClient *client, guint64 res); +void gst_rtsp_wfd_client_set_video_vesa_resolution(GstRTSPWFDClient *client, guint64 res); +void gst_rtsp_wfd_client_set_video_hh_resolution(GstRTSPWFDClient *client, guint64 res); +void gst_rtsp_wfd_client_set_video_profile(GstRTSPWFDClient *client, guint profile); +void gst_rtsp_wfd_client_set_video_level(GstRTSPWFDClient *client, guint level); +void gst_rtsp_wfd_client_set_video_latency(GstRTSPWFDClient *client, guint latency); +void gst_rtsp_wfd_client_set_video_max_height(GstRTSPWFDClient *client, guint32 height); +void gst_rtsp_wfd_client_set_video_max_width(GstRTSPWFDClient *client, guint32 width); +void gst_rtsp_wfd_client_set_video_framerate(GstRTSPWFDClient *client, guint32 framerate); +void gst_rtsp_wfd_client_set_video_min_slice_size(GstRTSPWFDClient *client, guint32 slice_size); +void gst_rtsp_wfd_client_set_video_slice_enc_params(GstRTSPWFDClient *client, guint32 enc_params); +void gst_rtsp_wfd_client_set_video_framerate_control(GstRTSPWFDClient *client, guint framerate); +void gst_rtsp_wfd_client_set_rtp_port0(GstRTSPWFDClient *client, guint32 port); +void gst_rtsp_wfd_client_set_rtp_port1(GstRTSPWFDClient *client, guint32 port); + +/** + * GstRTSPWFDClientSessionFilterFunc: + * @client: a #GstRTSPWFDClient object + * @sess: a #GstRTSPSession in @client + * @user_data: user data that has been given to gst_rtsp_wfd_client_session_filter() + * + * This function will be called by the gst_rtsp_wfd_client_session_filter(). An + * implementation should return a value of #GstRTSPFilterResult. + * + * When this function returns #GST_RTSP_FILTER_REMOVE, @sess will be removed + * from @client. + * + * A return value of #GST_RTSP_FILTER_KEEP will leave @sess untouched in + * @client. + * + * A value of #GST_RTSP_FILTER_REF will add @sess to the result #GList of + * gst_rtsp_wfd_client_session_filter(). + * + * Returns: a #GstRTSPFilterResult. + */ +typedef GstRTSPFilterResult (*GstRTSPWFDClientSessionFilterFunc) (GstRTSPWFDClient *client, + GstRTSPSession *sess, + gpointer user_data); + + + +G_END_DECLS + +#endif /* __GST_RTSP_WFD_CLIENT_H__ */ diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 55f6259..13ccf36 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -155,6 +155,12 @@ static GstRTSPResult default_params_get (GstRTSPClient * client, GstRTSPContext * ctx); static gchar *default_make_path_from_uri (GstRTSPClient * client, const GstRTSPUrl * uri); +static gboolean default_handle_options_request (GstRTSPClient * client, + GstRTSPContext * ctx); +static gboolean default_handle_set_param_request (GstRTSPClient * client, + GstRTSPContext * ctx); +static gboolean default_handle_get_param_request (GstRTSPClient * client, + GstRTSPContext * ctx); static void client_session_removed (GstRTSPSessionPool * pool, GstRTSPSession * session, GstRTSPClient * client); @@ -180,6 +186,9 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) klass->params_set = default_params_set; klass->params_get = default_params_get; klass->make_path_from_uri = default_make_path_from_uri; + klass->handle_options_request = default_handle_options_request; + klass->handle_set_param_request = default_handle_set_param_request; + klass->handle_get_param_request = default_handle_get_param_request; g_object_class_install_property (gobject_class, PROP_SESSION_POOL, g_param_spec_object ("session-pool", "Session Pool", @@ -929,7 +938,7 @@ default_params_get (GstRTSPClient * client, GstRTSPContext * ctx) } static gboolean -handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx) +default_handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPResult res; guint8 *data; @@ -966,7 +975,7 @@ bad_request: } static gboolean -handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx) +default_handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPResult res; guint8 *data; @@ -1833,6 +1842,8 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) if (media == NULL) goto media_not_found_no_reply; + /* FIXME-WFD : wfd url problem */ +#if 0 if (path[matched] == '\0') { if (gst_rtsp_media_n_streams (media) == 1) { stream = gst_rtsp_media_get_stream (media, 0); @@ -1848,6 +1859,12 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) /* find the stream now using the control part */ stream = gst_rtsp_media_find_stream (media, control); } +#else + control = g_strdup ("stream=0"); + + /* find the stream now using the control part */ + stream = gst_rtsp_media_find_stream (media, control); +#endif if (stream == NULL) goto stream_not_found; @@ -1944,6 +1961,10 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) /* create and serialize the server transport */ st = make_server_transport (client, media, ctx, ct); trans_str = gst_rtsp_transport_as_text (st); + + /* FIXME-WFD : Temporarily force to set profile string */ + trans_str = g_strjoinv ("RTP/AVP/UDP", g_strsplit (trans_str, "RTP/AVP", -1)); + gst_rtsp_transport_free (st); /* construct the response now */ @@ -2548,7 +2569,7 @@ unsuspend_failed: } static gboolean -handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx) +default_handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPMethod options; gchar *str; @@ -2704,6 +2725,9 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) GstRTSPMessage response = { 0 }; gchar *unsupported_reqs = NULL; gchar *sessid; + GstRTSPClientClass *klass; + + klass = GST_RTSP_CLIENT_GET_CLASS (client); if (!(ctx = gst_rtsp_context_get_current ())) { ctx = &sctx; @@ -2828,7 +2852,7 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) /* now see what is asked and dispatch to a dedicated handler */ switch (method) { case GST_RTSP_OPTIONS: - handle_options_request (client, ctx); + klass->handle_options_request (client, ctx); break; case GST_RTSP_DESCRIBE: handle_describe_request (client, ctx); @@ -2846,10 +2870,10 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) handle_teardown_request (client, ctx); break; case GST_RTSP_SET_PARAMETER: - handle_set_param_request (client, ctx); + klass->handle_set_param_request (client, ctx); break; case GST_RTSP_GET_PARAMETER: - handle_get_param_request (client, ctx); + klass->handle_get_param_request (client, ctx); break; case GST_RTSP_ANNOUNCE: handle_announce_request (client, ctx); diff --git a/gst/rtsp-server/rtsp-client.h b/gst/rtsp-server/rtsp-client.h index 6b05448..9b6d879 100644 --- a/gst/rtsp-server/rtsp-client.h +++ b/gst/rtsp-server/rtsp-client.h @@ -103,6 +103,9 @@ struct _GstRTSPClientClass { GstRTSPResult (*params_set) (GstRTSPClient *client, GstRTSPContext *ctx); GstRTSPResult (*params_get) (GstRTSPClient *client, GstRTSPContext *ctx); gchar * (*make_path_from_uri) (GstRTSPClient *client, const GstRTSPUrl *uri); + gboolean (*handle_options_request) (GstRTSPClient * client, GstRTSPContext * ctx); + gboolean (*handle_set_param_request) (GstRTSPClient * client, GstRTSPContext * ctx); + gboolean (*handle_get_param_request) (GstRTSPClient * client, GstRTSPContext * ctx); /* signals */ void (*closed) (GstRTSPClient *client); @@ -129,7 +132,7 @@ struct _GstRTSPClientClass { gchar* (*check_requirements) (GstRTSPClient *client, GstRTSPContext *ctx, gchar ** arr); /*< private >*/ - gpointer _gst_reserved[GST_PADDING_LARGE-6]; + gpointer _gst_reserved[GST_PADDING_LARGE-9]; }; GType gst_rtsp_client_get_type (void); diff --git a/gst/rtsp-server/rtsp-media-factory-wfd.c b/gst/rtsp-server/rtsp-media-factory-wfd.c new file mode 100644 index 0000000..f08a5c2 --- /dev/null +++ b/gst/rtsp-server/rtsp-media-factory-wfd.c @@ -0,0 +1,1200 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/** + * SECTION:rtsp-media-factory + * @short_description: A factory for media pipelines + * @see_also: #GstRTSPMountPoints, #GstRTSPMedia + * + * The #GstRTSPMediaFactoryWFD is responsible for creating or recycling + * #GstRTSPMedia objects based on the passed URL. + * + * The default implementation of the object can create #GstRTSPMedia objects + * containing a pipeline created from a launch description set with + * gst_rtsp_media_factory_wfd_set_launch(). + * + * Media from a factory can be shared by setting the shared flag with + * gst_rtsp_media_factory_wfd_set_shared(). When a factory is shared, + * gst_rtsp_media_factory_wfd_construct() will return the same #GstRTSPMedia when + * the url matches. + * + * Last reviewed on 2013-07-11 (1.0.0) + */ + +#include +#include "rtsp-media-factory-wfd.h" +#include "gstwfdmessage.h" + +#define GST_RTSP_MEDIA_FACTORY_WFD_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_WFD, GstRTSPMediaFactoryWFDPrivate)) + +#define GST_RTSP_MEDIA_FACTORY_WFD_GET_LOCK(f) (&(GST_RTSP_MEDIA_FACTORY_WFD_CAST(f)->priv->lock)) +#define GST_RTSP_MEDIA_FACTORY_WFD_LOCK(f) (g_mutex_lock(GST_RTSP_MEDIA_FACTORY_WFD_GET_LOCK(f))) +#define GST_RTSP_MEDIA_FACTORY_WFD_UNLOCK(f) (g_mutex_unlock(GST_RTSP_MEDIA_FACTORY_WFD_GET_LOCK(f))) + +struct _GstRTSPMediaFactoryWFDPrivate +{ + GMutex lock; + GstRTSPPermissions *permissions; + gchar *launch; + gboolean shared; + GstRTSPLowerTrans protocols; + guint buffer_size; + guint mtu_size; + + guint8 videosrc_type; + guint8 video_codec; + gchar *video_encoder; + guint video_bitrate; + guint video_width; + guint video_height; + guint video_framerate; + guint video_enc_skip_inbuf_value; + GstElement *video_queue; + + gchar *audio_device; + gchar *audio_encoder_aac; + gchar *audio_encoder_ac3; + guint8 audio_codec; + guint64 audio_latency_time; + guint64 audio_buffer_time; + gboolean audio_do_timestamp; + guint8 audio_channels; + guint8 audio_freq; + guint8 audio_bitrate; + GstElement *audio_queue; + + guint64 video_resolution_supported; + + gboolean dump_ts; +}; + +#define DEFAULT_LAUNCH NULL +#define DEFAULT_SHARED FALSE +#define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \ + GST_RTSP_LOWER_TRANS_TCP +#define DEFAULT_BUFFER_SIZE 0x80000 + +enum +{ + PROP_0, + PROP_LAUNCH, + PROP_SHARED, + PROP_SUSPEND_MODE, + PROP_EOS_SHUTDOWN, + PROP_PROTOCOLS, + PROP_BUFFER_SIZE, + PROP_LAST +}; + +enum +{ + SIGNAL_MEDIA_CONSTRUCTED, + SIGNAL_MEDIA_CONFIGURE, + SIGNAL_LAST +}; + +GST_DEBUG_CATEGORY_STATIC (rtsp_media_wfd_debug); +#define GST_CAT_DEFAULT rtsp_media_wfd_debug + +static void gst_rtsp_media_factory_wfd_get_property (GObject * object, + guint propid, GValue * value, GParamSpec * pspec); +static void gst_rtsp_media_factory_wfd_set_property (GObject * object, + guint propid, const GValue * value, GParamSpec * pspec); + +static void gst_rtsp_media_factory_wfd_finalize (GObject * obj); + + +static GstElement *rtsp_media_factory_wfd_create_element (GstRTSPMediaFactory * + factory, const GstRTSPUrl * url); +static GstRTSPMedia *rtsp_media_factory_wfd_construct (GstRTSPMediaFactory * + factory, const GstRTSPUrl * url); + +G_DEFINE_TYPE (GstRTSPMediaFactoryWFD, gst_rtsp_media_factory_wfd, + GST_TYPE_RTSP_MEDIA_FACTORY); + +static void +gst_rtsp_media_factory_wfd_class_init (GstRTSPMediaFactoryWFDClass * klass) +{ + GObjectClass *gobject_class; + GstRTSPMediaFactoryClass *factory_class; + + g_type_class_add_private (klass, sizeof (GstRTSPMediaFactoryWFDPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + factory_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass); + + gobject_class->get_property = gst_rtsp_media_factory_wfd_get_property; + gobject_class->set_property = gst_rtsp_media_factory_wfd_set_property; + gobject_class->finalize = gst_rtsp_media_factory_wfd_finalize; + + factory_class->construct = rtsp_media_factory_wfd_construct; + factory_class->create_element = rtsp_media_factory_wfd_create_element; + + GST_DEBUG_CATEGORY_INIT (rtsp_media_wfd_debug, "rtspmediafactorywfd", 0, + "GstRTSPMediaFactoryWFD"); +} + +void gst_rtsp_media_factory_wfd_set (GstRTSPMediaFactoryWFD * factory, + guint8 videosrc_type, gchar *audio_device, guint64 audio_latency_time, + guint64 audio_buffer_time, gboolean audio_do_timestamp, guint mtu_size) +{ + GstRTSPMediaFactoryWFDPrivate *priv = + GST_RTSP_MEDIA_FACTORY_WFD_GET_PRIVATE (factory); + factory->priv = priv; + + priv->videosrc_type = videosrc_type; + priv->audio_device = audio_device; + priv->audio_latency_time = audio_latency_time; + priv->audio_buffer_time = audio_buffer_time; + priv->audio_do_timestamp = audio_do_timestamp; + priv->mtu_size = mtu_size; +} + +void gst_rtsp_media_factory_wfd_set_encoders (GstRTSPMediaFactoryWFD * factory, + gchar *video_encoder, gchar *audio_encoder_aac, gchar *audio_encoder_ac3) +{ + GstRTSPMediaFactoryWFDPrivate *priv = + GST_RTSP_MEDIA_FACTORY_WFD_GET_PRIVATE (factory); + factory->priv = priv; + + priv->video_encoder = video_encoder; + priv->audio_encoder_aac = audio_encoder_aac; + priv->audio_encoder_ac3 = audio_encoder_ac3; +} + +void gst_rtsp_media_factory_wfd_set_dump_ts (GstRTSPMediaFactoryWFD * factory, + gboolean dump_ts) +{ + GstRTSPMediaFactoryWFDPrivate *priv = + GST_RTSP_MEDIA_FACTORY_WFD_GET_PRIVATE (factory); + factory->priv = priv; + + priv->dump_ts = dump_ts; +} +void gst_rtsp_media_factory_wfd_set_negotiated_resolution (GstRTSPMediaFactory *factory, + guint32 width, guint32 height) +{ + GstRTSPMediaFactoryWFD *factory_wfd = GST_RTSP_MEDIA_FACTORY_WFD (factory); + GstRTSPMediaFactoryWFDPrivate *priv = factory_wfd->priv; + + priv->video_width = width; + priv->video_height = height; +} +void gst_rtsp_media_factory_wfd_set_audio_codec (GstRTSPMediaFactory *factory, + guint audio_codec) +{ + GstRTSPMediaFactoryWFD *factory_wfd = GST_RTSP_MEDIA_FACTORY_WFD (factory); + GstRTSPMediaFactoryWFDPrivate *priv = factory_wfd->priv; + + priv->audio_codec = audio_codec; +} + +static void +gst_rtsp_media_factory_wfd_init (GstRTSPMediaFactoryWFD * factory) +{ + GstRTSPMediaFactoryWFDPrivate *priv = + GST_RTSP_MEDIA_FACTORY_WFD_GET_PRIVATE (factory); + factory->priv = priv; + + priv->launch = g_strdup (DEFAULT_LAUNCH); + priv->shared = DEFAULT_SHARED; + priv->protocols = DEFAULT_PROTOCOLS; + priv->buffer_size = DEFAULT_BUFFER_SIZE; + + //priv->videosrc_type = GST_WFD_VSRC_XIMAGESRC; + //priv->videosrc_type = GST_WFD_VSRC_XVIMAGESRC; + //priv->videosrc_type = GST_WFD_VSRC_CAMERASRC; + priv->videosrc_type = GST_WFD_VSRC_VIDEOTESTSRC; + priv->video_codec = GST_WFD_VIDEO_H264; + priv->video_encoder = g_strdup ("omxh264enc"); + priv->video_bitrate = 200000; + priv->video_width = 640; + priv->video_height = 480; + priv->video_framerate = 30; + priv->video_enc_skip_inbuf_value = 5; + + priv->audio_device = g_strdup ("alsa_output.1.analog-stereo.monitor"); + priv->audio_codec = GST_WFD_AUDIO_AAC; + priv->audio_encoder_aac = g_strdup ("avenc_aac"); + priv->audio_encoder_ac3 = g_strdup ("avenc_ac3"); + priv->audio_latency_time = 10000; + priv->audio_buffer_time = 200000; + priv->audio_do_timestamp = FALSE; + priv->audio_channels = GST_WFD_CHANNEL_2; + priv->audio_freq = GST_WFD_FREQ_48000; + + g_mutex_init (&priv->lock); +} + +static void +gst_rtsp_media_factory_wfd_finalize (GObject * obj) +{ + GstRTSPMediaFactoryWFD *factory = GST_RTSP_MEDIA_FACTORY_WFD (obj); + GstRTSPMediaFactoryWFDPrivate *priv = factory->priv; + + if (priv->permissions) + gst_rtsp_permissions_unref (priv->permissions); + g_free (priv->launch); + g_mutex_clear (&priv->lock); + + if (priv->audio_device) + g_free (priv->audio_device); + if (priv->audio_encoder_aac) + g_free (priv->audio_encoder_aac); + if (priv->audio_encoder_ac3) + g_free (priv->audio_encoder_ac3); + + if (priv->video_encoder) + g_free (priv->video_encoder); + + G_OBJECT_CLASS (gst_rtsp_media_factory_wfd_parent_class)->finalize (obj); +} + +GstRTSPMediaFactoryWFD * +gst_rtsp_media_factory_wfd_new (void) +{ + GstRTSPMediaFactoryWFD *result; + + result = g_object_new (GST_TYPE_RTSP_MEDIA_FACTORY_WFD, NULL); + + return result; +} + +static void +gst_rtsp_media_factory_wfd_get_property (GObject * object, + guint propid, GValue * value, GParamSpec * pspec) +{ + //GstRTSPMediaFactoryWFD *factory = GST_RTSP_MEDIA_FACTORY_WFD (object); + + switch (propid) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static void +gst_rtsp_media_factory_wfd_set_property (GObject * object, + guint propid, const GValue * value, GParamSpec * pspec) +{ + //GstRTSPMediaFactoryWFD *factory = GST_RTSP_MEDIA_FACTORY_WFD (object); + + switch (propid) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static GstPadProbeReturn +rtsp_media_wfd_dump_data (GstPad * pad, GstPadProbeInfo *info, gpointer u_data) +{ + guint8 *data; + gsize size; + FILE *f; + GstMapInfo mapinfo; + + if (info->type == (GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PUSH)) { + GstBuffer *buffer = gst_pad_probe_info_get_buffer (info); + + gst_buffer_map (buffer, &mapinfo, GST_MAP_READ); + data = mapinfo.data; + size = gst_buffer_get_size (buffer); + + f = fopen ("/root/probe.ts", "a"); + if (f != NULL) { + fwrite (data, size, 1, f); + fclose (f); + } + gst_buffer_unmap (buffer, &mapinfo); + } + + return GST_PAD_PROBE_OK; +} + +static gboolean +_rtsp_media_factory_wfd_create_audio_capture_bin (GstRTSPMediaFactoryWFD * + factory, GstBin * srcbin) +{ + GstElement *audiosrc = NULL; + GstElement *acaps = NULL; + GstElement *acaps2 = NULL; + GstElement *aenc = NULL; + GstElement *audio_convert = NULL; + GstElement *aqueue = NULL; + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + guint channels = 0; + gboolean is_enc_req = TRUE; + guint freq = 0; + gchar *acodec = NULL; + + priv = factory->priv; + + /* create audio src element */ + audiosrc = gst_element_factory_make ("pulsesrc", "audiosrc"); + if (!audiosrc) { + GST_ERROR_OBJECT (factory, "failed to create audiosrc element"); + goto create_error; + } + + GST_INFO_OBJECT (factory, "audio device : %s", priv->audio_device); + GST_INFO_OBJECT (factory, "audio latency time : %"G_GUINT64_FORMAT, + priv->audio_latency_time); + GST_INFO_OBJECT (factory, "audio_buffer_time : %"G_GUINT64_FORMAT, + priv->audio_buffer_time); + GST_INFO_OBJECT (factory, "audio_do_timestamp : %d", + priv->audio_do_timestamp); + + g_object_set (audiosrc, "device", priv->audio_device, NULL); + g_object_set (audiosrc, "buffer-time", (gint64) priv->audio_buffer_time, + NULL); + g_object_set (audiosrc, "latency-time", (gint64) priv->audio_latency_time, + NULL); + g_object_set (audiosrc, "do-timestamp", (gboolean) priv->audio_do_timestamp, + NULL); + g_object_set (audiosrc, "provide-clock", (gboolean) FALSE, NULL); + g_object_set (audiosrc, "is-live", (gboolean) TRUE, NULL); + + if (priv->audio_codec == GST_WFD_AUDIO_LPCM) { + /* To meet miracast certification */ + gint64 block_size = 1920; + g_object_set (audiosrc, "blocksize", (gint64) block_size, NULL); + + audio_convert = gst_element_factory_make ("capssetter", "audio_convert"); + if (NULL == audio_convert) { + GST_ERROR_OBJECT (factory, "failed to create audio convert element"); + goto create_error; + } + g_object_set (audio_convert, "caps", gst_caps_new_simple("audio/x-lpcm", + "width", G_TYPE_INT, 16, + "rate", G_TYPE_INT, 48000, + "channels", G_TYPE_INT, 2, + "dynamic_range", G_TYPE_INT, 0, + "emphasis", G_TYPE_BOOLEAN, FALSE, + "mute", G_TYPE_BOOLEAN, FALSE, NULL), NULL); + g_object_set (audio_convert, "join", (gboolean)FALSE, NULL); + g_object_set (audio_convert, "replace", (gboolean)TRUE, NULL); + + acaps2 = gst_element_factory_make ("capsfilter", "audiocaps2"); + if (NULL == acaps2) { + GST_ERROR_OBJECT (factory, "failed to create audio capsilfter element"); + goto create_error; + } + /* In case of LPCM, uses big endian */ + g_object_set (G_OBJECT (acaps2), "caps", + gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, "S16BE", + /* In case of LPCM, uses big endian */ + "rate", G_TYPE_INT, 48000, + "channels", G_TYPE_INT, 2, NULL), NULL); + } + + /* create audio caps element */ + acaps = gst_element_factory_make ("capsfilter", "audiocaps"); + if (NULL == acaps) { + GST_ERROR_OBJECT (factory, "failed to create audio capsilfter element"); + goto create_error; + } + + if (priv->audio_channels == GST_WFD_CHANNEL_2) + channels = 2; + else if (priv->audio_channels == GST_WFD_CHANNEL_4) + channels = 4; + else if (priv->audio_channels == GST_WFD_CHANNEL_6) + channels = 6; + else if (priv->audio_channels == GST_WFD_CHANNEL_8) + channels = 8; + else + channels = 2; + + if (priv->audio_freq == GST_WFD_FREQ_44100) + freq = 44100; + else if (priv->audio_freq == GST_WFD_FREQ_48000) + freq = 48000; + else + freq = 44100; + + if (priv->audio_codec == GST_WFD_AUDIO_LPCM) { + g_object_set (G_OBJECT (acaps), "caps", + gst_caps_new_simple ("audio/x-lpcm", "width", G_TYPE_INT, 16, + "rate", G_TYPE_INT, 48000, + "channels", G_TYPE_INT, 2, + "dynamic_range", G_TYPE_INT, 0, + "emphasis", G_TYPE_BOOLEAN, FALSE, + "mute", G_TYPE_BOOLEAN, FALSE, NULL), NULL); + } else if ((priv->audio_codec == GST_WFD_AUDIO_AAC) + || (priv->audio_codec == GST_WFD_AUDIO_AC3)) { + g_object_set (G_OBJECT (acaps), "caps", gst_caps_new_simple ("audio/x-raw", + "endianness", G_TYPE_INT, 1234, "signed", G_TYPE_BOOLEAN, TRUE, + "depth", G_TYPE_INT, 16, "rate", G_TYPE_INT, freq, "channels", + G_TYPE_INT, channels, NULL), NULL); + } + + if (priv->audio_codec == GST_WFD_AUDIO_AAC) { + acodec = g_strdup (priv->audio_encoder_aac); + is_enc_req = TRUE; + } else if (priv->audio_codec == GST_WFD_AUDIO_AC3) { + acodec = g_strdup (priv->audio_encoder_ac3); + is_enc_req = TRUE; + } else if (priv->audio_codec == GST_WFD_AUDIO_LPCM) { + GST_DEBUG_OBJECT (factory, "No codec required, raw data will be sent"); + is_enc_req = FALSE; + } else { + GST_ERROR_OBJECT (factory, "Yet to support other than H264 format"); + goto create_error; + } + + if (is_enc_req) { + aenc = gst_element_factory_make (acodec, "audioenc"); + if (NULL == aenc) { + GST_ERROR_OBJECT (factory, "failed to create audio encoder element"); + goto create_error; + } + + g_object_set (aenc, "compliance", -2, NULL); + g_object_set (aenc, "tolerance", 400000000, NULL); + g_object_set (aenc, "bitrate", (guint) 128000, NULL); + g_object_set (aenc, "rate-control", 2, NULL); + + aqueue = gst_element_factory_make ("queue", "audio-queue"); + if (!aqueue) { + GST_ERROR_OBJECT (factory, "failed to create audio queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, audiosrc, acaps, aenc, aqueue, NULL); + + if (!gst_element_link_many (audiosrc, acaps, aenc, aqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link audio src elements..."); + goto create_error; + } + } else { + aqueue = gst_element_factory_make ("queue", "audio-queue"); + if (!aqueue) { + GST_ERROR_OBJECT (factory, "failed to create audio queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, audiosrc, acaps2, audio_convert, acaps, aqueue, NULL); + + if (!gst_element_link_many (audiosrc, acaps2, audio_convert, acaps, aqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link audio src elements..."); + goto create_error; + } + } + + priv->audio_queue = aqueue; + if (acodec) g_free (acodec); + + return TRUE; + +create_error: + if (acodec) g_free (acodec); + return FALSE; +} + +static gboolean +_rtsp_media_factory_wfd_create_videotest_bin (GstRTSPMediaFactoryWFD * factory, + GstBin * srcbin) +{ + GstElement *videosrc = NULL; + GstElement *vcaps = NULL; + GstElement *videoconvert = NULL; + GstElement *venc_caps = NULL; + gchar *vcodec = NULL; + GstElement *venc = NULL; + GstElement *vparse = NULL; + GstElement *vqueue = NULL; + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + priv = factory->priv; + + GST_INFO_OBJECT (factory, "picked videotestsrc as video source"); + + videosrc = gst_element_factory_make ("videotestsrc", "videosrc"); + if (NULL == videosrc) { + GST_ERROR_OBJECT (factory, "failed to create ximagesrc element"); + goto create_error; + } + + /* create video caps element */ + vcaps = gst_element_factory_make ("capsfilter", "videocaps"); + if (NULL == vcaps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + g_object_set (G_OBJECT (vcaps), "caps", + gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "I420", + "width", G_TYPE_INT, priv->video_width, + "height", G_TYPE_INT, priv->video_height, + "framerate", GST_TYPE_FRACTION, priv->video_framerate, 1, NULL), + NULL); + + /* create video convert element */ + videoconvert = gst_element_factory_make ("videoconvert", "videoconvert"); + if (NULL == videoconvert) { + GST_ERROR_OBJECT (factory, "failed to create video videoconvert element"); + goto create_error; + } + + venc_caps = gst_element_factory_make ("capsfilter", "venc_caps"); + if (NULL == venc_caps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + g_object_set (G_OBJECT (venc_caps), "caps", + gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "SN12", + "width", G_TYPE_INT, priv->video_width, + "height", G_TYPE_INT, priv->video_height, + "framerate", GST_TYPE_FRACTION, priv->video_framerate, 1, NULL), + NULL); + + if (priv->video_codec == GST_WFD_VIDEO_H264) + vcodec = g_strdup (priv->video_encoder); + else { + GST_ERROR_OBJECT (factory, "Yet to support other than H264 format"); + goto create_error; + } + + venc = gst_element_factory_make (vcodec, "videoenc"); + if (vcodec) g_free (vcodec); + + if (!venc) { + GST_ERROR_OBJECT (factory, "failed to create video encoder element"); + goto create_error; + } + + g_object_set (venc, "aud", 0, NULL); + g_object_set (venc, "byte-stream", 1, NULL); + g_object_set (venc, "bitrate", 512, NULL); + + vparse = gst_element_factory_make ("h264parse", "videoparse"); + if (NULL == vparse) { + GST_ERROR_OBJECT (factory, "failed to create h264 parse element"); + goto create_error; + } + g_object_set (vparse, "config-interval", 1, NULL); + + vqueue = gst_element_factory_make ("queue", "video-queue"); + if (!vqueue) { + GST_ERROR_OBJECT (factory, "failed to create video queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, videosrc, vcaps, videoconvert, venc_caps, venc, vparse, vqueue, NULL); + if (!gst_element_link_many (videosrc, vcaps, videoconvert, venc_caps, venc, vparse, vqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link video src elements..."); + goto create_error; + } + + priv->video_queue = vqueue; + + return TRUE; + +create_error: + return FALSE; +} + +static gboolean +_rtsp_media_factory_wfd_create_waylandsrc_bin (GstRTSPMediaFactoryWFD * factory, + GstBin * srcbin) +{ + GstElement *videosrc = NULL; + GstElement *vcaps = NULL; + gchar *vcodec = NULL; + GstElement *venc = NULL; + GstElement *vparse = NULL; + GstElement *vqueue = NULL; + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + priv = factory->priv; + + GST_INFO_OBJECT (factory, "picked waylandsrc as video source"); + + videosrc = gst_element_factory_make ("waylandsrc", "videosrc"); + if (NULL == videosrc) { + GST_ERROR_OBJECT (factory, "failed to create ximagesrc element"); + goto create_error; + } + + /* create video caps element */ + vcaps = gst_element_factory_make ("capsfilter", "videocaps"); + if (NULL == vcaps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + g_object_set (G_OBJECT (vcaps), "caps", + gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "SN12", + "width", G_TYPE_INT, priv->video_width, + "height", G_TYPE_INT, priv->video_height, + "framerate", GST_TYPE_FRACTION, priv->video_framerate, 1, NULL), + NULL); + + if (priv->video_codec == GST_WFD_VIDEO_H264) + vcodec = g_strdup (priv->video_encoder); + else { + GST_ERROR_OBJECT (factory, "Yet to support other than H264 format"); + goto create_error; + } + + venc = gst_element_factory_make (vcodec, "videoenc"); + if (vcodec) g_free (vcodec); + + if (!venc) { + GST_ERROR_OBJECT (factory, "failed to create video encoder element"); + goto create_error; + } + + g_object_set (venc, "aud", 0, NULL); + g_object_set (venc, "byte-stream", 1, NULL); + g_object_set (venc, "bitrate", 512, NULL); + + vparse = gst_element_factory_make ("h264parse", "videoparse"); + if (NULL == vparse) { + GST_ERROR_OBJECT (factory, "failed to create h264 parse element"); + goto create_error; + } + g_object_set (vparse, "config-interval", 1, NULL); + + vqueue = gst_element_factory_make ("queue", "video-queue"); + if (!vqueue) { + GST_ERROR_OBJECT (factory, "failed to create video queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, videosrc, vcaps, venc, vparse, vqueue, NULL); + if (!gst_element_link_many (videosrc, vcaps, venc, vparse, vqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link video src elements..."); + goto create_error; + } + + priv->video_queue = vqueue; + + return TRUE; + +create_error: + return FALSE; +} + +static gboolean +_rtsp_media_factory_wfd_create_camera_capture_bin (GstRTSPMediaFactoryWFD * + factory, GstBin * srcbin) +{ + GstElement *videosrc = NULL; + GstElement *vcaps = NULL; + GstElement *venc = NULL; + GstElement *vparse = NULL; + GstElement *vqueue = NULL; + gchar *vcodec = NULL; + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + priv = factory->priv; + + videosrc = gst_element_factory_make ("camerasrc", "videosrc"); + if (NULL == videosrc) { + GST_ERROR_OBJECT (factory, "failed to create camerasrc element"); + goto create_error; + } + + /* create video caps element */ + vcaps = gst_element_factory_make ("capsfilter", "videocaps"); + if (NULL == vcaps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + GST_INFO_OBJECT (factory, "picked camerasrc as video source"); + g_object_set (G_OBJECT (vcaps), "caps", + gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, priv->video_width, + "height", G_TYPE_INT, priv->video_height, + "format", G_TYPE_STRING, "SN12", + "framerate", GST_TYPE_FRACTION, priv->video_framerate, 1, NULL), + NULL); + + if (priv->video_codec == GST_WFD_VIDEO_H264) + vcodec = g_strdup (priv->video_encoder); + else { + GST_ERROR_OBJECT (factory, "Yet to support other than H264 format"); + goto create_error; + } + + venc = gst_element_factory_make (vcodec, "videoenc"); + if (!venc) { + GST_ERROR_OBJECT (factory, "failed to create video encoder element"); + goto create_error; + } + if (vcodec) g_free (vcodec); + + g_object_set (venc, "bitrate", priv->video_bitrate, NULL); + g_object_set (venc, "byte-stream", 1, NULL); + g_object_set (venc, "append-dci", 1, NULL); + + vparse = gst_element_factory_make ("h264parse", "videoparse"); + if (NULL == vparse) { + GST_ERROR_OBJECT (factory, "failed to create h264 parse element"); + goto create_error; + } + g_object_set (vparse, "config-interval", 1, NULL); + + vqueue = gst_element_factory_make ("queue", "video-queue"); + if (!vqueue) { + GST_ERROR_OBJECT (factory, "failed to create video queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, videosrc, vcaps, venc, vparse, vqueue, NULL); + + if (!gst_element_link_many (videosrc, vcaps, venc, vparse, vqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link video src elements..."); + goto create_error; + } + + priv->video_queue = vqueue; + + return TRUE; + +create_error: + return FALSE; +} + +static gboolean +_rtsp_media_factory_wfd_create_xcapture_bin (GstRTSPMediaFactoryWFD * factory, + GstBin * srcbin) +{ + GstElement *videosrc = NULL; + GstElement *vcaps = NULL; + GstElement *venc_caps = NULL; + GstElement *videoconvert = NULL, *videoscale = NULL; + gchar *vcodec = NULL; + GstElement *venc = NULL; + GstElement *vparse = NULL; + GstElement *vqueue = NULL; + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + priv = factory->priv; + + GST_INFO_OBJECT (factory, "picked ximagesrc as video source"); + + videosrc = gst_element_factory_make ("ximagesrc", "videosrc"); + if (NULL == videosrc) { + GST_ERROR_OBJECT (factory, "failed to create ximagesrc element"); + goto create_error; + } + + videoscale = gst_element_factory_make ("videoscale", "videoscale"); + if (NULL == videoscale) { + GST_ERROR_OBJECT (factory, "failed to create videoscale element"); + goto create_error; + } + + videoconvert = gst_element_factory_make ("videoconvert", "videoconvert"); + if (NULL == videoconvert) { + GST_ERROR_OBJECT (factory, "failed to create videoconvert element"); + goto create_error; + } + + /* create video caps element */ + vcaps = gst_element_factory_make ("capsfilter", "videocaps"); + if (NULL == vcaps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + g_object_set (G_OBJECT (vcaps), "caps", + gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, priv->video_width, + "height", G_TYPE_INT, priv->video_height, + "framerate", GST_TYPE_FRACTION, priv->video_framerate, 1, NULL), + NULL); + + if (priv->video_codec == GST_WFD_VIDEO_H264) + vcodec = g_strdup (priv->video_encoder); + else { + GST_ERROR_OBJECT (factory, "Yet to support other than H264 format"); + goto create_error; + } + + venc = gst_element_factory_make (vcodec, "videoenc"); + if (vcodec) g_free (vcodec); + + if (!venc) { + GST_ERROR_OBJECT (factory, "failed to create video encoder element"); + goto create_error; + } + + g_object_set (venc, "aud", 0, NULL); + g_object_set (venc, "byte-stream", 1, NULL); + g_object_set (venc, "bitrate", 512, NULL); + + venc_caps = gst_element_factory_make ("capsfilter", "venc_caps"); + if (NULL == venc_caps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + g_object_set (G_OBJECT (venc_caps), "caps", + gst_caps_new_simple ("video/x-h264", + "profile", G_TYPE_STRING, "baseline", NULL), NULL); + + vparse = gst_element_factory_make ("h264parse", "videoparse"); + if (NULL == vparse) { + GST_ERROR_OBJECT (factory, "failed to create h264 parse element"); + goto create_error; + } + g_object_set (vparse, "config-interval", 1, NULL); + + vqueue = gst_element_factory_make ("queue", "video-queue"); + if (!vqueue) { + GST_ERROR_OBJECT (factory, "failed to create video queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, videosrc, videoscale, videoconvert, vcaps, venc, + venc_caps, vparse, vqueue, NULL); + if (!gst_element_link_many (videosrc, videoscale, videoconvert, vcaps, venc, + venc_caps, vparse, vqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link video src elements..."); + goto create_error; + } + + priv->video_queue = vqueue; + + return TRUE; + +create_error: + return FALSE; +} + +static gboolean +_rtsp_media_factory_wfd_create_xvcapture_bin (GstRTSPMediaFactoryWFD * factory, + GstBin * srcbin) +{ + GstElement *videosrc = NULL; + GstElement *vcaps = NULL; + gchar *vcodec = NULL; + GstElement *venc = NULL; + GstElement *vparse = NULL; + GstElement *vqueue = NULL; + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + priv = factory->priv; + + GST_INFO_OBJECT (factory, "picked xvimagesrc as video source"); + + videosrc = gst_element_factory_make ("xvimagesrc", "videosrc"); + if (NULL == videosrc) { + GST_ERROR_OBJECT (factory, "failed to create xvimagesrc element"); + goto create_error; + } + + /* create video caps element */ + vcaps = gst_element_factory_make ("capsfilter", "videocaps"); + if (NULL == vcaps) { + GST_ERROR_OBJECT (factory, "failed to create video capsilfter element"); + goto create_error; + } + + g_object_set (G_OBJECT (vcaps), "caps", + gst_caps_new_simple ("video/x-raw", + "width", G_TYPE_INT, priv->video_width, + "height", G_TYPE_INT, priv->video_height, + "format", G_TYPE_STRING, "SN12", + "framerate", GST_TYPE_FRACTION, priv->video_framerate, 1, NULL), + NULL); + + if (priv->video_codec == GST_WFD_VIDEO_H264) { + vcodec = g_strdup (priv->video_encoder); + } else { + GST_ERROR_OBJECT (factory, "Yet to support other than H264 format"); + goto create_error; + } + + venc = gst_element_factory_make (vcodec, "videoenc"); + if (!venc) { + GST_ERROR_OBJECT (factory, "failed to create video encoder element"); + goto create_error; + } + g_object_set (venc, "bitrate", priv->video_bitrate, NULL); + g_object_set (venc, "byte-stream", 1, NULL); + g_object_set (venc, "append-dci", 1, NULL); + g_object_set (venc, "idr-period", 120, NULL); + g_object_set (venc, "skip-inbuf", priv->video_enc_skip_inbuf_value, NULL); + + vparse = gst_element_factory_make ("h264parse", "videoparse"); + if (NULL == vparse) { + GST_ERROR_OBJECT (factory, "failed to create h264 parse element"); + goto create_error; + } + g_object_set (vparse, "config-interval", 1, NULL); + + vqueue = gst_element_factory_make ("queue", "video-queue"); + if (!vqueue) { + GST_ERROR_OBJECT (factory, "failed to create video queue element"); + goto create_error; + } + + gst_bin_add_many (srcbin, videosrc, vcaps, venc, vparse, vqueue, NULL); + if (!gst_element_link_many (videosrc, vcaps, venc, vparse, vqueue, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link video src elements..."); + goto create_error; + } + + priv->video_queue = vqueue; + if (vcodec) g_free (vcodec); + + return TRUE; + +create_error: + if (vcodec) g_free (vcodec); + return FALSE; +} + +static GstElement * +_rtsp_media_factory_wfd_create_srcbin (GstRTSPMediaFactoryWFD * factory) +{ + GstRTSPMediaFactoryWFDPrivate *priv = NULL; + + GstBin *srcbin = NULL; + GstElement *mux = NULL; + GstElement *mux_queue = NULL; + GstElement *payload = NULL; + GstPad *srcpad = NULL; + GstPad *mux_vsinkpad = NULL; + GstPad *mux_asinkpad = NULL; + + priv = factory->priv; + + /* create source bin */ + srcbin = GST_BIN (gst_bin_new ("srcbin")); + if (!srcbin) { + GST_ERROR_OBJECT (factory, "failed to create source bin..."); + goto create_error; + } + + /* create video src element */ + switch (priv->videosrc_type) { + case GST_WFD_VSRC_XIMAGESRC: + if (!_rtsp_media_factory_wfd_create_xcapture_bin (factory, srcbin)) { + GST_ERROR_OBJECT (factory, "failed to create xcapture bin..."); + goto create_error; + } + break; + case GST_WFD_VSRC_XVIMAGESRC: + if (!_rtsp_media_factory_wfd_create_xvcapture_bin (factory, srcbin)) { + GST_ERROR_OBJECT (factory, "failed to create xvcapture bin..."); + goto create_error; + } + break; + case GST_WFD_VSRC_CAMERASRC: + if (!_rtsp_media_factory_wfd_create_camera_capture_bin (factory, srcbin)) { + GST_ERROR_OBJECT (factory, "failed to create camera capture bin..."); + goto create_error; + } + break; + case GST_WFD_VSRC_VIDEOTESTSRC: + if (!_rtsp_media_factory_wfd_create_videotest_bin (factory, srcbin)) { + GST_ERROR_OBJECT (factory, "failed to create videotestsrc bin..."); + goto create_error; + } + break; + case GST_WFD_VSRC_WAYLANDSRC: + if (!_rtsp_media_factory_wfd_create_waylandsrc_bin (factory, srcbin)) { + GST_ERROR_OBJECT (factory, "failed to create videotestsrc bin..."); + goto create_error; + } + break; + default: + GST_ERROR_OBJECT (factory, "unknow mode selected..."); + goto create_error; + } + + mux = gst_element_factory_make ("mpegtsmux", "tsmux"); + if (!mux) { + GST_ERROR_OBJECT (factory, "failed to create muxer element"); + goto create_error; + } + + g_object_set (mux, "wfd-mode", TRUE, NULL); + + mux_queue = gst_element_factory_make ("queue", "muxer-queue"); + if (!mux_queue) { + GST_ERROR_OBJECT (factory, "failed to create muxer-queue element"); + goto create_error; + } + + g_object_set (mux_queue, "max-size-buffers", 20000, NULL); + + payload = gst_element_factory_make ("rtpmp2tpay", "pay0"); + if (!payload) { + GST_ERROR_OBJECT (factory, "failed to create payload element"); + goto create_error; + } + + g_object_set (payload, "pt", 33, NULL); + g_object_set (payload, "mtu", priv->mtu_size, NULL); + g_object_set (payload, "rtp-flush", (gboolean) TRUE, NULL); + + gst_bin_add_many (srcbin, mux, mux_queue, payload, NULL); + + if (!gst_element_link_many (mux, mux_queue, payload, NULL)) { + GST_ERROR_OBJECT (factory, "Failed to link muxer & payload..."); + goto create_error; + } + + /* request video sink pad from muxer, which has elementary pid 0x1011 */ + mux_vsinkpad = gst_element_get_request_pad (mux, "sink_4113"); + if (!mux_vsinkpad) { + GST_ERROR_OBJECT (factory, "Failed to get sink pad from muxer..."); + goto create_error; + } + + /* request srcpad from video queue */ + srcpad = gst_element_get_static_pad (priv->video_queue, "src"); + if (!srcpad) { + GST_ERROR_OBJECT (factory, "Failed to get srcpad from video queue..."); + goto create_error; + } + + if (gst_pad_link (srcpad, mux_vsinkpad) != GST_PAD_LINK_OK) { + GST_ERROR_OBJECT (factory, + "Failed to link video queue src pad & muxer video sink pad..."); + goto create_error; + } + + gst_object_unref (mux_vsinkpad); + gst_object_unref (srcpad); + srcpad = NULL; + + /* create audio source elements & add to pipeline */ + if (!_rtsp_media_factory_wfd_create_audio_capture_bin (factory, srcbin)) + goto create_error; + + /* request audio sink pad from muxer, which has elementary pid 0x1100 */ + mux_asinkpad = gst_element_get_request_pad (mux, "sink_4352"); + if (!mux_asinkpad) { + GST_ERROR_OBJECT (factory, "Failed to get sinkpad from muxer..."); + goto create_error; + } + + /* request srcpad from audio queue */ + srcpad = gst_element_get_static_pad (priv->audio_queue, "src"); + if (!srcpad) { + GST_ERROR_OBJECT (factory, "Failed to get srcpad from audio queue..."); + goto create_error; + } + + /* link audio queue's srcpad & muxer sink pad */ + if (gst_pad_link (srcpad, mux_asinkpad) != GST_PAD_LINK_OK) { + GST_ERROR_OBJECT (factory, + "Failed to link audio queue src pad & muxer audio sink pad..."); + goto create_error; + } + gst_object_unref (mux_asinkpad); + gst_object_unref (srcpad); + + if (priv->dump_ts) + { + GstPad *pad_probe = NULL; + pad_probe = gst_element_get_static_pad (mux, "src"); + + if (NULL == pad_probe) { + GST_INFO_OBJECT (factory, "pad for probe not created"); + } else { + GST_INFO_OBJECT (factory, "pad for probe SUCCESSFUL"); + } + gst_pad_add_probe (pad_probe, GST_PAD_PROBE_TYPE_BUFFER, + rtsp_media_wfd_dump_data, factory, NULL); + } + + GST_DEBUG_OBJECT (factory, "successfully created source bin..."); + + return GST_ELEMENT_CAST (srcbin); + +create_error: + GST_ERROR_OBJECT (factory, "Failed to create pipeline"); + return NULL; +} + +static GstElement * +rtsp_media_factory_wfd_create_element (GstRTSPMediaFactory * factory, + const GstRTSPUrl * url) +{ + GstRTSPMediaFactoryWFD *_factory = GST_RTSP_MEDIA_FACTORY_WFD_CAST (factory); + GstElement *element = NULL; + + GST_RTSP_MEDIA_FACTORY_WFD_LOCK (factory); + + element = _rtsp_media_factory_wfd_create_srcbin (_factory); + + GST_RTSP_MEDIA_FACTORY_WFD_UNLOCK (factory); + + return element; +} + +static GstRTSPMedia * +rtsp_media_factory_wfd_construct (GstRTSPMediaFactory * factory, + const GstRTSPUrl * url) +{ + GstRTSPMedia *media; + GstElement *element, *pipeline; + GstRTSPMediaFactoryClass *klass; + + klass = GST_RTSP_MEDIA_FACTORY_GET_CLASS (factory); + + if (!klass->create_pipeline) + goto no_create; + + element = gst_rtsp_media_factory_create_element (factory, url); + if (element == NULL) + goto no_element; + + /* create a new empty media */ + media = gst_rtsp_media_new (element); + + gst_rtsp_media_collect_streams (media); + + pipeline = klass->create_pipeline (factory, media); + if (pipeline == NULL) + goto no_pipeline; + + return media; + + /* ERRORS */ +no_create: + { + g_critical ("no create_pipeline function"); + return NULL; + } +no_element: + { + g_critical ("could not create element"); + return NULL; + } +no_pipeline: + { + g_critical ("can't create pipeline"); + g_object_unref (media); + return NULL; + } +} diff --git a/gst/rtsp-server/rtsp-media-factory-wfd.h b/gst/rtsp-server/rtsp-media-factory-wfd.h new file mode 100644 index 0000000..1751a22 --- /dev/null +++ b/gst/rtsp-server/rtsp-media-factory-wfd.h @@ -0,0 +1,131 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include "rtsp-media-factory.h" + +#ifndef __GST_RTSP_MEDIA_FACTORY_WFD_H__ +#define __GST_RTSP_MEDIA_FACTORY_WFD_H__ + +G_BEGIN_DECLS +/* types for the media factory */ +#define GST_TYPE_RTSP_MEDIA_FACTORY_WFD (gst_rtsp_media_factory_wfd_get_type ()) +#define GST_IS_RTSP_MEDIA_FACTORY_WFD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_WFD)) +#define GST_IS_RTSP_MEDIA_FACTORY_WFD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA_FACTORY_WFD)) +#define GST_RTSP_MEDIA_FACTORY_WFD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_WFD, GstRTSPMediaFactoryWFDClass)) +#define GST_RTSP_MEDIA_FACTORY_WFD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA_FACTORY_WFD, GstRTSPMediaFactoryWFD)) +#define GST_RTSP_MEDIA_FACTORY_WFD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA_FACTORY_WFD, GstRTSPMediaFactoryWFDClass)) +#define GST_RTSP_MEDIA_FACTORY_WFD_CAST(obj) ((GstRTSPMediaFactoryWFD*)(obj)) +#define GST_RTSP_MEDIA_FACTORY_WFD_CLASS_CAST(klass) ((GstRTSPMediaFactoryWFDClass*)(klass)) + enum +{ + GST_WFD_VSRC_XIMAGESRC, + GST_WFD_VSRC_XVIMAGESRC, + GST_WFD_VSRC_CAMERASRC, + GST_WFD_VSRC_VIDEOTESTSRC, + GST_WFD_VSRC_WAYLANDSRC +}; + +typedef struct _GstRTSPMediaFactoryWFD GstRTSPMediaFactoryWFD; +typedef struct _GstRTSPMediaFactoryWFDClass GstRTSPMediaFactoryWFDClass; +typedef struct _GstRTSPMediaFactoryWFDPrivate GstRTSPMediaFactoryWFDPrivate; + +/** + * GstRTSPMediaFactoryWFD: + * + * The definition and logic for constructing the pipeline for a media. The media + * can contain multiple streams like audio and video. + */ +struct _GstRTSPMediaFactoryWFD +{ + GstRTSPMediaFactory parent; + + /*< private > */ + GstRTSPMediaFactoryWFDPrivate *priv; + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstRTSPMediaFactoryWFDClass: + * @gen_key: convert @url to a key for caching shared #GstRTSPMedia objects. + * The default implementation of this function will use the complete URL + * including the query parameters to return a key. + * @create_element: Construct and return a #GstElement that is a #GstBin containing + * the elements to use for streaming the media. The bin should contain + * payloaders pay\%d for each stream. The default implementation of this + * function returns the bin created from the launch parameter. + * @construct: the vmethod that will be called when the factory has to create the + * #GstRTSPMedia for @url. The default implementation of this + * function calls create_element to retrieve an element and then looks for + * pay\%d to create the streams. + * @create_pipeline: create a new pipeline or re-use an existing one and + * add the #GstRTSPMedia's element created by @construct to the pipeline. + * @configure: configure the media created with @construct. The default + * implementation will configure the 'shared' property of the media. + * @media_constructed: signal emited when a media was constructed + * @media_configure: signal emited when a media should be configured + * + * The #GstRTSPMediaFactoryWFD class structure. + */ +struct _GstRTSPMediaFactoryWFDClass +{ + GstRTSPMediaFactoryClass parent_class; + + gchar *(*gen_key) (GstRTSPMediaFactoryWFD * factory, const GstRTSPUrl * url); + + GstElement *(*create_element) (GstRTSPMediaFactoryWFD * factory, + const GstRTSPUrl * url); + GstRTSPMedia *(*construct) (GstRTSPMediaFactoryWFD * factory, + const GstRTSPUrl * url); + GstElement *(*create_pipeline) (GstRTSPMediaFactoryWFD * factory, + GstRTSPMedia * media); + void (*configure) (GstRTSPMediaFactoryWFD * factory, GstRTSPMedia * media); + + /* signals */ + void (*media_constructed) (GstRTSPMediaFactoryWFD * factory, + GstRTSPMedia * media); + void (*media_configure) (GstRTSPMediaFactoryWFD * factory, + GstRTSPMedia * media); + + /*< private > */ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + +GType gst_rtsp_media_factory_wfd_get_type (void); + +/* creating the factory */ +GstRTSPMediaFactoryWFD *gst_rtsp_media_factory_wfd_new (void); +GstElement *gst_rtsp_media_factory_wfd_create_element (GstRTSPMediaFactoryWFD * + factory, const GstRTSPUrl * url); + +void gst_rtsp_media_factory_wfd_set (GstRTSPMediaFactoryWFD * factory, + guint8 videosrc_type, gchar *audio_device, guint64 audio_latency_time, + guint64 audio_buffer_time, gboolean audio_do_timestamp, guint mtu_size); +void gst_rtsp_media_factory_wfd_set_encoders (GstRTSPMediaFactoryWFD * factory, + gchar *video_encoder, gchar *audio_encoder_aac, gchar *audio_encoder_ac3); +void gst_rtsp_media_factory_wfd_set_dump_ts (GstRTSPMediaFactoryWFD * factory, + gboolean dump_ts); +void gst_rtsp_media_factory_wfd_set_negotiated_resolution (GstRTSPMediaFactory *factory, + guint32 width, guint32 height); +void gst_rtsp_media_factory_wfd_set_audio_codec (GstRTSPMediaFactory *factory, + guint audio_codec); + +G_END_DECLS +#endif /* __GST_RTSP_MEDIA_FACTORY_WFD_H__ */ diff --git a/gst/rtsp-server/rtsp-server-wfd.c b/gst/rtsp-server/rtsp-server-wfd.c new file mode 100644 index 0000000..4e72093 --- /dev/null +++ b/gst/rtsp-server/rtsp-server-wfd.c @@ -0,0 +1,332 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/** + * SECTION:rtsp-server + * @short_description: The main server object + * @see_also: #GstRTSPClient, #GstRTSPThreadPool + * + * The server object is the object listening for connections on a port and + * creating #GstRTSPClient objects to handle those connections. + * + * The server will listen on the address set with gst_rtsp_server_set_address() + * and the port or service configured with gst_rtsp_server_set_service(). + * Use gst_rtsp_server_set_backlog() to configure the amount of pending requests + * that the server will keep. By default the server listens on the current + * network (0.0.0.0) and port 8554. + * + * The server will require an SSL connection when a TLS certificate has been + * set in the auth object with gst_rtsp_auth_set_tls_certificate(). + * + * To start the server, use gst_rtsp_server_attach() to attach it to a + * #GMainContext. For more control, gst_rtsp_server_create_source() and + * gst_rtsp_server_create_socket() can be used to get a #GSource and #GSocket + * respectively. + * + * gst_rtsp_server_transfer_connection() can be used to transfer an existing + * socket to the RTSP server, for example from an HTTP server. + * + * Once the server socket is attached to a mainloop, it will start accepting + * connections. When a new connection is received, a new #GstRTSPClient object + * is created to handle the connection. The new client will be configured with + * the server #GstRTSPAuth, #GstRTSPMountPoints, #GstRTSPSessionPool and + * #GstRTSPThreadPool. + * + * The server uses the configured #GstRTSPThreadPool object to handle the + * remainder of the communication with this client. + * + * Last reviewed on 2013-07-11 (1.0.0) + */ +#include +#include + +#include "rtsp-server-wfd.h" +#include "rtsp-client-wfd.h" + +#define GST_RTSP_WFD_SERVER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_WFD_SERVER, GstRTSPWFDServerPrivate)) + +#define GST_RTSP_WFD_SERVER_GET_LOCK(server) (&(GST_RTSP_WFD_SERVER_CAST(server)->priv->lock)) +#define GST_RTSP_WFD_SERVER_LOCK(server) (g_mutex_lock(GST_RTSP_WFD_SERVER_GET_LOCK(server))) +#define GST_RTSP_WFD_SERVER_UNLOCK(server) (g_mutex_unlock(GST_RTSP_WFD_SERVER_GET_LOCK(server))) + +struct _GstRTSPWFDServerPrivate +{ + GMutex lock; /* protects everything in this struct */ + + /* the clients that are connected */ + GList *clients; + guint64 native_resolution; + guint64 supported_resolution; + guint8 audio_codec; +}; + +G_DEFINE_TYPE (GstRTSPWFDServer, gst_rtsp_wfd_server, GST_TYPE_RTSP_SERVER); + +GST_DEBUG_CATEGORY_STATIC (rtsp_wfd_server_debug); +#define GST_CAT_DEFAULT rtsp_wfd_server_debug + +static void gst_rtsp_wfd_server_get_property (GObject * object, guint propid, + GValue * value, GParamSpec * pspec); +static void gst_rtsp_wfd_server_set_property (GObject * object, guint propid, + const GValue * value, GParamSpec * pspec); +static void gst_rtsp_wfd_server_finalize (GObject * object); + +static GstRTSPClient *create_client_wfd (GstRTSPServer * server); +static void client_connected_wfd (GstRTSPServer * server, + GstRTSPClient * client); + +static void +gst_rtsp_wfd_server_class_init (GstRTSPWFDServerClass * klass) +{ + GObjectClass *gobject_class; + GstRTSPServerClass *rtsp_server_class; + + g_type_class_add_private (klass, sizeof (GstRTSPWFDServerPrivate)); + + gobject_class = G_OBJECT_CLASS (klass); + rtsp_server_class = GST_RTSP_SERVER_CLASS (klass); + + gobject_class->get_property = gst_rtsp_wfd_server_get_property; + gobject_class->set_property = gst_rtsp_wfd_server_set_property; + gobject_class->finalize = gst_rtsp_wfd_server_finalize; + + rtsp_server_class->create_client = create_client_wfd; + rtsp_server_class->client_connected = client_connected_wfd; + + + GST_DEBUG_CATEGORY_INIT (rtsp_wfd_server_debug, "rtspwfdserver", 0, + "GstRTSPWFDServer"); +} + +static void +gst_rtsp_wfd_server_init (GstRTSPWFDServer * server) +{ + GstRTSPWFDServerPrivate *priv = GST_RTSP_WFD_SERVER_GET_PRIVATE (server); + + g_return_if_fail (priv != NULL); + + server->priv = priv; + server->priv->native_resolution = 0; + server->priv->supported_resolution = 1; + server->priv->audio_codec = 2; + GST_INFO_OBJECT (server, "New server is initialized"); +} + +static void +gst_rtsp_wfd_server_finalize (GObject * object) +{ + GstRTSPWFDServer *server = GST_RTSP_WFD_SERVER (object); + //GstRTSPWFDServerPrivate *priv = server->priv; + + GST_DEBUG_OBJECT (server, "finalize server"); + + G_OBJECT_CLASS (gst_rtsp_wfd_server_parent_class)->finalize (object); +} + +/** + * gst_rtsp_server_new: + * + * Create a new #GstRTSPWFDServer instance. + */ +GstRTSPWFDServer * +gst_rtsp_wfd_server_new (void) +{ + GstRTSPWFDServer *result; + + result = g_object_new (GST_TYPE_RTSP_WFD_SERVER, NULL); + + return result; +} + +static void +gst_rtsp_wfd_server_get_property (GObject * object, guint propid, + GValue * value, GParamSpec * pspec) +{ + //GstRTSPWFDServer *server = GST_RTSP_WFD_SERVER (object); + + switch (propid) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static void +gst_rtsp_wfd_server_set_property (GObject * object, guint propid, + const GValue * value, GParamSpec * pspec) +{ + //GstRTSPWFDServer *server = GST_RTSP_WFD_SERVER (object); + + switch (propid) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +static gboolean +_start_wfd (gpointer data) +{ + GstRTSPWFDClient *client = (GstRTSPWFDClient *) data; + + GST_INFO_OBJECT (client, "WFD client is STARTing"); + + gst_rtsp_wfd_client_start_wfd (client); + return FALSE; +} + +static void +client_connected_wfd (GstRTSPServer * server, GstRTSPClient * client) +{ + GST_INFO_OBJECT (server, "Client is connected"); + + gst_rtsp_wfd_client_set_host_address (GST_RTSP_WFD_CLIENT_CAST (client), + gst_rtsp_server_get_address (server)); + g_idle_add (_start_wfd, client); + return; +} + +static GstRTSPClient * +create_client_wfd (GstRTSPServer * server) +{ + GstRTSPWFDClient *client; + GstRTSPThreadPool *thread_pool = NULL; + GstRTSPSessionPool *session_pool = NULL; + GstRTSPMountPoints *mount_points = NULL; + GstRTSPAuth *auth = NULL; + GstRTSPWFDServerPrivate *priv = GST_RTSP_WFD_SERVER_GET_PRIVATE(server); + + g_return_val_if_fail (priv != NULL, NULL); + + GST_INFO_OBJECT (server, "New Client is being created"); + + /* a new client connected, create a session to handle the client. */ + client = gst_rtsp_wfd_client_new (); + + thread_pool = gst_rtsp_server_get_thread_pool (server); + session_pool = gst_rtsp_server_get_session_pool (server); + mount_points = gst_rtsp_server_get_mount_points (server); + auth = gst_rtsp_server_get_auth (server); + + /* set the session pool that this client should use */ + GST_RTSP_WFD_SERVER_LOCK (server); + gst_rtsp_client_set_session_pool (GST_RTSP_CLIENT_CAST (client), + session_pool); + /* set the mount points that this client should use */ + gst_rtsp_client_set_mount_points (GST_RTSP_CLIENT_CAST (client), + mount_points); + /* set authentication manager */ + gst_rtsp_client_set_auth (GST_RTSP_CLIENT_CAST (client), auth); + /* set threadpool */ + gst_rtsp_client_set_thread_pool (GST_RTSP_CLIENT_CAST (client), thread_pool); + + gst_rtsp_wfd_client_set_video_supported_resolution (client, + priv->supported_resolution); + + gst_rtsp_wfd_client_set_video_native_resolution (client, + priv->native_resolution); + + gst_rtsp_wfd_client_set_audio_codec (client, + priv->audio_codec); + + GST_RTSP_WFD_SERVER_UNLOCK (server); + + return GST_RTSP_CLIENT (client); +} + +GstRTSPResult +gst_rtsp_wfd_server_trigger_request (GstRTSPServer * server, + GstWFDTriggerType type) +{ + GstRTSPResult res = GST_RTSP_OK; + GList *clients, *walk, *next; + + g_return_val_if_fail (GST_IS_RTSP_SERVER (server), GST_RTSP_ERROR); + + clients = gst_rtsp_server_client_filter (server, NULL, NULL); + if (clients == NULL) { + GST_ERROR_OBJECT (server, "There is no client in this server"); + } + + for (walk = clients; walk; walk = next) { + GstRTSPClient *client = walk->data; + + next = g_list_next (walk); + + res = + gst_rtsp_wfd_client_trigger_request (GST_RTSP_WFD_CLIENT (client), + type); + if (res != GST_RTSP_OK) { + GST_ERROR_OBJECT (server, "Failed to send trigger request %d", type); + } + g_object_unref (client); + } + + return res; + +} + +GstRTSPResult +gst_rtsp_wfd_server_set_supported_reso(GstRTSPWFDServer *server, guint64 supported_reso) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDServerPrivate *priv = GST_RTSP_WFD_SERVER_GET_PRIVATE(server); + + g_return_val_if_fail (GST_IS_RTSP_WFD_SERVER (server), GST_RTSP_ERROR); + g_return_val_if_fail (priv != NULL, GST_RTSP_ERROR); + + GST_RTSP_WFD_SERVER_LOCK (server); + + priv->supported_resolution = supported_reso; + + GST_RTSP_WFD_SERVER_UNLOCK (server); + return res; +} + +GstRTSPResult +gst_rtsp_wfd_server_set_video_native_reso (GstRTSPWFDServer *server, guint64 native_reso) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDServerPrivate *priv = GST_RTSP_WFD_SERVER_GET_PRIVATE(server); + + g_return_val_if_fail (GST_IS_RTSP_WFD_SERVER (server), GST_RTSP_ERROR); + g_return_val_if_fail (priv != NULL, GST_RTSP_ERROR); + + GST_RTSP_WFD_SERVER_LOCK (server); + + priv->native_resolution = native_reso; + + GST_RTSP_WFD_SERVER_UNLOCK (server); + return res; +} + +GstRTSPResult +gst_rtsp_wfd_server_set_audio_codec (GstRTSPWFDServer *server, guint8 audio_codec) +{ + GstRTSPResult res = GST_RTSP_OK; + GstRTSPWFDServerPrivate *priv = GST_RTSP_WFD_SERVER_GET_PRIVATE(server); + + g_return_val_if_fail (GST_IS_RTSP_WFD_SERVER (server), GST_RTSP_ERROR); + g_return_val_if_fail (priv != NULL, GST_RTSP_ERROR); + + GST_RTSP_WFD_SERVER_LOCK (server); + + priv->audio_codec = audio_codec; + + GST_RTSP_WFD_SERVER_UNLOCK (server); + return res; +} diff --git a/gst/rtsp-server/rtsp-server-wfd.h b/gst/rtsp-server/rtsp-server-wfd.h new file mode 100644 index 0000000..e718e62 --- /dev/null +++ b/gst/rtsp-server/rtsp-server-wfd.h @@ -0,0 +1,155 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Hyunjun Ko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTSP_SERVER_WFD_H__ +#define __GST_RTSP_SERVER_WFD_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstRTSPWFDServer GstRTSPWFDServer; +typedef struct _GstRTSPWFDServerClass GstRTSPWFDServerClass; +typedef struct _GstRTSPWFDServerPrivate GstRTSPWFDServerPrivate; + +#include "rtsp-session-pool.h" +#include "rtsp-mount-points.h" +#include "rtsp-server.h" +#include "rtsp-client-wfd.h" +#include "rtsp-auth.h" + +#define GST_TYPE_RTSP_WFD_SERVER (gst_rtsp_wfd_server_get_type ()) +#define GST_IS_RTSP_WFD_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_WFD_SERVER)) +#define GST_IS_RTSP_WFD_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_WFD_SERVER)) +#define GST_RTSP_WFD_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_WFD_SERVER, GstRTSPWFDServerClass)) +#define GST_RTSP_WFD_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_WFD_SERVER, GstRTSPWFDServer)) +#define GST_RTSP_WFD_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_WFD_SERVER, GstRTSPWFDServerClass)) +#define GST_RTSP_WFD_SERVER_CAST(obj) ((GstRTSPWFDServer*)(obj)) +#define GST_RTSP_WFD_SERVER_CLASS_CAST(klass) ((GstRTSPWFDServerClass*)(klass)) + +/** + * GstRTSPWFDServer: + * + * This object listens on a port, creates and manages the clients connected to + * it. + */ +struct _GstRTSPWFDServer { + GstRTSPServer parent; + + /*< private >*/ + GstRTSPWFDServerPrivate *priv; + gpointer _gst_reserved[GST_PADDING]; +}; + +/** + * GstRTSPServerClass: + * @create_client: Create, configure a new GstRTSPClient + * object that handles the new connection on @socket. The default + * implementation will create a GstRTSPClient and will configure the + * mount-points, auth, session-pool and thread-pool on the client. + * @client_connected: emited when a new client connected. + * + * The RTSP server class structure + */ +struct _GstRTSPWFDServerClass { + GstRTSPServerClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + +GType gst_rtsp_wfd_server_get_type (void); +GstRTSPWFDServer * gst_rtsp_wfd_server_new (void); +GstRTSPResult gst_rtsp_wfd_server_trigger_request (GstRTSPServer *server, GstWFDTriggerType type); + +GstRTSPResult gst_rtsp_wfd_server_set_supported_reso (GstRTSPWFDServer *server, guint64 supported_reso); +GstRTSPResult gst_rtsp_wfd_server_set_video_native_reso (GstRTSPWFDServer *server, guint64 native_reso); +GstRTSPResult gst_rtsp_wfd_server_set_audio_codec (GstRTSPWFDServer *server, guint8 audio_codec); + +#if 0 +void gst_rtsp_server_set_address (GstRTSPServer *server, const gchar *address); +gchar * gst_rtsp_server_get_address (GstRTSPServer *server); + +void gst_rtsp_server_set_service (GstRTSPServer *server, const gchar *service); +gchar * gst_rtsp_server_get_service (GstRTSPServer *server); + +void gst_rtsp_server_set_backlog (GstRTSPServer *server, gint backlog); +gint gst_rtsp_server_get_backlog (GstRTSPServer *server); + +int gst_rtsp_server_get_bound_port (GstRTSPServer *server); + +void gst_rtsp_server_set_session_pool (GstRTSPServer *server, GstRTSPSessionPool *pool); +GstRTSPSessionPool * gst_rtsp_server_get_session_pool (GstRTSPServer *server); + +void gst_rtsp_server_set_mount_points (GstRTSPServer *server, GstRTSPMountPoints *mounts); +GstRTSPMountPoints * gst_rtsp_server_get_mount_points (GstRTSPServer *server); + +void gst_rtsp_server_set_auth (GstRTSPServer *server, GstRTSPAuth *auth); +GstRTSPAuth * gst_rtsp_server_get_auth (GstRTSPServer *server); + +void gst_rtsp_server_set_thread_pool (GstRTSPServer *server, GstRTSPThreadPool *pool); +GstRTSPThreadPool * gst_rtsp_server_get_thread_pool (GstRTSPServer *server); + +gboolean gst_rtsp_server_transfer_connection (GstRTSPServer * server, GSocket *socket, + const gchar * ip, gint port, + const gchar *initial_buffer); + +gboolean gst_rtsp_server_io_func (GSocket *socket, GIOCondition condition, + GstRTSPServer *server); + +GSocket * gst_rtsp_server_create_socket (GstRTSPServer *server, + GCancellable *cancellable, + GError **error); +GSource * gst_rtsp_server_create_source (GstRTSPServer *server, + GCancellable * cancellable, + GError **error); +guint gst_rtsp_server_attach (GstRTSPServer *server, + GMainContext *context); +/** + * GstRTSPServerClientFilterFunc: + * @server: a #GstRTSPServer object + * @client: a #GstRTSPClient in @server + * @user_data: user data that has been given to gst_rtsp_server_client_filter() + * + * This function will be called by the gst_rtsp_server_client_filter(). An + * implementation should return a value of #GstRTSPFilterResult. + * + * When this function returns #GST_RTSP_FILTER_REMOVE, @client will be removed + * from @server. + * + * A return value of #GST_RTSP_FILTER_KEEP will leave @client untouched in + * @server. + * + * A value of #GST_RTSP_FILTER_REF will add @client to the result #GList of + * gst_rtsp_server_client_filter(). + * + * Returns: a #GstRTSPFilterResult. + */ +typedef GstRTSPFilterResult (*GstRTSPServerClientFilterFunc) (GstRTSPServer *server, + GstRTSPClient *client, + gpointer user_data); + +GList * gst_rtsp_server_client_filter (GstRTSPServer *server, + GstRTSPServerClientFilterFunc func, + gpointer user_data); +#endif + +G_END_DECLS + +#endif /* __GST_RTSP_SERVER_WFD_H__ */ diff --git a/gst/rtsp-server/rtsp-stream.c b/gst/rtsp-server/rtsp-stream.c index 1c4366e..4c1f3b7 100644 --- a/gst/rtsp-server/rtsp-stream.c +++ b/gst/rtsp-server/rtsp-stream.c @@ -1103,6 +1103,9 @@ again: inetaddr = g_inet_address_new_any (family); } + /* FIXME-WFD : Force to set 19000 as port number */ + tmp_rtp = 19000; + rtp_sockaddr = g_inet_socket_address_new (inetaddr, tmp_rtp); if (!g_socket_bind (rtp_socket, rtp_sockaddr, FALSE, NULL)) { g_object_unref (rtp_sockaddr); @@ -1280,10 +1283,15 @@ alloc_ports (GstRTSPStream * stream) G_SOCKET_FAMILY_IPV4, priv->udpsrc_v4, priv->udpsink, &priv->server_port_v4, &priv->server_addr_v4); + /* FIXME-WFD : force to disable ipv6 mode in WFD mode */ +#if 0 priv->have_ipv6 = alloc_ports_one_family (stream, priv->pool, priv->buffer_size, G_SOCKET_FAMILY_IPV6, priv->udpsrc_v6, priv->udpsink, &priv->server_port_v6, &priv->server_addr_v6); +#else + priv->have_ipv6 = FALSE; +#endif return priv->have_ipv4 || priv->have_ipv6; } @@ -3154,6 +3162,21 @@ gst_rtsp_stream_get_current_seqnum (GstRTSPStream * stream) return seqnum; } +guint64 +gst_rtsp_stream_get_udp_sent_bytes (GstRTSPStream *stream) +{ + GstRTSPStreamPrivate *priv; + guint64 bytes = 0; + + g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0); + + priv = stream->priv; + + g_object_get (G_OBJECT (priv->udpsink[0]), "bytes-to-serve", &bytes, NULL); + + return bytes; +} + /** * gst_rtsp_stream_transport_filter: * @stream: a #GstRTSPStream diff --git a/gst/rtsp-server/rtsp-stream.h b/gst/rtsp-server/rtsp-stream.h index 0042f67..eca190c 100644 --- a/gst/rtsp-server/rtsp-stream.h +++ b/gst/rtsp-server/rtsp-stream.h @@ -155,6 +155,7 @@ gboolean gst_rtsp_stream_query_stop (GstRTSPStream * stream, void gst_rtsp_stream_set_seqnum_offset (GstRTSPStream *stream, guint16 seqnum); guint16 gst_rtsp_stream_get_current_seqnum (GstRTSPStream *stream); +guint64 gst_rtsp_stream_get_udp_sent_bytes (GstRTSPStream *stream); void gst_rtsp_stream_set_retransmission_time (GstRTSPStream *stream, GstClockTime time); GstClockTime gst_rtsp_stream_get_retransmission_time (GstRTSPStream *stream); guint gst_rtsp_stream_get_retransmission_pt (GstRTSPStream * stream); diff --git a/packaging/common.tar.bz2 b/packaging/common.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..9f0dd3fdd986c996f08bccd391eca5d9ed5a1858 GIT binary patch literal 102577 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 e0ZuZ3ZeEm9%(cLDxnBU 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 VK>pD~V5oop7ji{7P>_|6fIKYB7@7b8 literal 0 HcmV?d00001 diff --git a/packaging/gst-rtsp-server.spec b/packaging/gst-rtsp-server.spec new file mode 100644 index 0000000..0474352 --- /dev/null +++ b/packaging/gst-rtsp-server.spec @@ -0,0 +1,76 @@ +Name: gst-rtsp-server +Summary: Multimedia Framework Library +Version: 1.6.1 +Release: 0 +Url: http://gstreamer.freedesktop.org/ +Group: System/Libraries +License: LGPL-2.0+ +Source: http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-%{version}.tar.xz +Source100: common.tar.bz2 +Requires(post): /sbin/ldconfig +Requires(postun): /sbin/ldconfig +BuildRequires: pkgconfig(gstreamer-1.0) +BuildRequires: pkgconfig(gstreamer-plugins-base-1.0) + +BuildRoot: %{_tmppath}/%{name}-%{version}-build + +%description + +%package devel +Summary: Multimedia Framework RTSP server library (DEV) +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description devel + +%package factory +Summary: Multimedia Framework RTSP server Library (Factory) +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description factory + +%prep +%setup -q -n gst-rtsp-server-%{version} +%setup -q -T -D -a 100 + +%build + +NOCONFIGURE=1 ./autogen.sh + +CFLAGS+=" -DEXPORT_API=\"__attribute__((visibility(\\\"default\\\")))\" "; export CFLAGS +LDFLAGS+="-Wl,--rpath=%{_prefix}/lib -Wl,--hash-style=both -Wl,--as-needed"; export LDFLAGS + +# always enable sdk build. This option should go away +%configure --disable-static + +# Call make instruction with smp support +make %{?jobs:-j%jobs} + +%install +rm -rf %{buildroot} +%make_install +mkdir -p %{buildroot}/%{_datadir}/license +cp -rf %{_builddir}/%{name}-%{version}/COPYING %{buildroot}%{_datadir}/license/%{name} + +%clean +rm -rf %{buildroot} + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%manifest gst-rtsp-server.manifest +%defattr(-,root,root,-) +%{_datadir}/license/%{name} +%{_libdir}/*.so.* + +%files devel +%defattr(-,root,root,-) +%{_libdir}/*.so +%{_includedir}/gstreamer-1.0/gst/rtsp-server/rtsp-*.h +%{_includedir}/gstreamer-1.0/gst/rtsp-server/gstwfd*.h +%{_libdir}/pkgconfig/* -- 2.7.4 From b109694c1d78f9c7883aa934ce53221c96739a17 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Thu, 3 Dec 2015 17:51:02 +0900 Subject: [PATCH 02/16] client: makes handle_play_request inheritable Change-Id: I1a0449d7610d1d1019eee880ed18873c148f0d0c --- gst/rtsp-server/rtsp-client.c | 8 ++++++-- gst/rtsp-server/rtsp-client.h | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 13ccf36..05ff274 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -161,6 +161,9 @@ static gboolean default_handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx); static gboolean default_handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx); +static gboolean default_handle_play_request (GstRTSPClient * client, + GstRTSPContext * ctx); + static void client_session_removed (GstRTSPSessionPool * pool, GstRTSPSession * session, GstRTSPClient * client); @@ -189,6 +192,7 @@ gst_rtsp_client_class_init (GstRTSPClientClass * klass) klass->handle_options_request = default_handle_options_request; klass->handle_set_param_request = default_handle_set_param_request; klass->handle_get_param_request = default_handle_get_param_request; + klass->handle_play_request = default_handle_play_request; g_object_class_install_property (gobject_class, PROP_SESSION_POOL, g_param_spec_object ("session-pool", "Session Pool", @@ -1127,7 +1131,7 @@ make_base_url (GstRTSPClient * client, GstRTSPUrl * url, const gchar * path) } static gboolean -handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) +default_handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx) { GstRTSPSession *session; GstRTSPClientClass *klass; @@ -2861,7 +2865,7 @@ handle_request (GstRTSPClient * client, GstRTSPMessage * request) handle_setup_request (client, ctx); break; case GST_RTSP_PLAY: - handle_play_request (client, ctx); + klass->handle_play_request (client, ctx); break; case GST_RTSP_PAUSE: handle_pause_request (client, ctx); diff --git a/gst/rtsp-server/rtsp-client.h b/gst/rtsp-server/rtsp-client.h index 9b6d879..13fa2d1 100644 --- a/gst/rtsp-server/rtsp-client.h +++ b/gst/rtsp-server/rtsp-client.h @@ -106,6 +106,7 @@ struct _GstRTSPClientClass { gboolean (*handle_options_request) (GstRTSPClient * client, GstRTSPContext * ctx); gboolean (*handle_set_param_request) (GstRTSPClient * client, GstRTSPContext * ctx); gboolean (*handle_get_param_request) (GstRTSPClient * client, GstRTSPContext * ctx); + gboolean (*handle_play_request) (GstRTSPClient * client, GstRTSPContext * ctx); /* signals */ void (*closed) (GstRTSPClient *client); @@ -132,7 +133,7 @@ struct _GstRTSPClientClass { gchar* (*check_requirements) (GstRTSPClient *client, GstRTSPContext *ctx, gchar ** arr); /*< private >*/ - gpointer _gst_reserved[GST_PADDING_LARGE-9]; + gpointer _gst_reserved[GST_PADDING_LARGE-10]; }; GType gst_rtsp_client_get_type (void); -- 2.7.4 From 2e221d5eb45afa3d4aea5bc3b18c9da3f7f72e78 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Thu, 3 Dec 2015 17:51:38 +0900 Subject: [PATCH 03/16] stream: Remove one FIXME-WFD, which could be resolved for user to set address pool Change-Id: Ibd48e6f585bf5be9be72ddda4b6a4fc75abc9103 --- gst/rtsp-server/rtsp-stream.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/gst/rtsp-server/rtsp-stream.c b/gst/rtsp-server/rtsp-stream.c index 4c1f3b7..9805c1e 100644 --- a/gst/rtsp-server/rtsp-stream.c +++ b/gst/rtsp-server/rtsp-stream.c @@ -1103,9 +1103,6 @@ again: inetaddr = g_inet_address_new_any (family); } - /* FIXME-WFD : Force to set 19000 as port number */ - tmp_rtp = 19000; - rtp_sockaddr = g_inet_socket_address_new (inetaddr, tmp_rtp); if (!g_socket_bind (rtp_socket, rtp_sockaddr, FALSE, NULL)) { g_object_unref (rtp_sockaddr); -- 2.7.4 From 267446a6e04437f16e56731e99d54625063a11d6 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Thu, 3 Dec 2015 17:52:03 +0900 Subject: [PATCH 04/16] Fix svace defects Change-Id: Ibc6aec1fd85e3d4d539f8e5fa207b39c73740b3b --- gst/rtsp-server/rtsp-client-wfd.c | 12 ++++++------ gst/rtsp-server/rtsp-media-factory-wfd.c | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c index 561a722..38cbae1 100644 --- a/gst/rtsp-server/rtsp-client-wfd.c +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -2717,7 +2717,7 @@ void gst_rtsp_wfd_client_set_audio_freq(GstRTSPWFDClient *client, guint freq) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); - g_return_val_if_fail (priv != NULL, 0); + g_return_if_fail (priv != NULL); priv->cFreq = freq; } @@ -2726,7 +2726,7 @@ void gst_rtsp_wfd_client_set_edid_supported(GstRTSPWFDClient *client, gboolean supported) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); - g_return_val_if_fail (priv != NULL, 0); + g_return_if_fail (priv != NULL); priv->edid_supported = supported; } @@ -2735,7 +2735,7 @@ void gst_rtsp_wfd_client_set_edid_hresolution(GstRTSPWFDClient *client, guint32 reso) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); - g_return_val_if_fail (priv != NULL, 0); + g_return_if_fail (priv != NULL); priv->edid_hres = reso; } @@ -2744,7 +2744,7 @@ void gst_rtsp_wfd_client_set_edid_vresolution(GstRTSPWFDClient *client, guint32 reso) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); - g_return_val_if_fail (priv != NULL, 0); + g_return_if_fail (priv != NULL); priv->edid_vres = reso; } @@ -2753,7 +2753,7 @@ void gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean enable) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); - g_return_val_if_fail (priv != NULL, 0); + g_return_if_fail (priv != NULL); priv->protection_enabled = enable; } @@ -2761,7 +2761,7 @@ gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean en void gst_rtsp_wfd_client_set_keep_alive_flag(GstRTSPWFDClient *client, gboolean flag) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); - g_return_val_if_fail (priv != NULL, 0); + g_return_if_fail (priv != NULL); g_mutex_lock(&priv->keep_alive_lock); if (priv->keep_alive_flag == !(flag)) diff --git a/gst/rtsp-server/rtsp-media-factory-wfd.c b/gst/rtsp-server/rtsp-media-factory-wfd.c index f08a5c2..0b7e76d 100644 --- a/gst/rtsp-server/rtsp-media-factory-wfd.c +++ b/gst/rtsp-server/rtsp-media-factory-wfd.c @@ -740,11 +740,12 @@ _rtsp_media_factory_wfd_create_camera_capture_bin (GstRTSPMediaFactoryWFD * } venc = gst_element_factory_make (vcodec, "videoenc"); + if (vcodec) g_free (vcodec); + if (!venc) { GST_ERROR_OBJECT (factory, "failed to create video encoder element"); goto create_error; } - if (vcodec) g_free (vcodec); g_object_set (venc, "bitrate", priv->video_bitrate, NULL); g_object_set (venc, "byte-stream", 1, NULL); -- 2.7.4 From 735ba122b8a0ee5bc2a00b6ac7084ebe58ce8ed1 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Thu, 10 Dec 2015 16:31:25 +0900 Subject: [PATCH 05/16] Remove FIXME-WFD temporary code Change-Id: Ib59e88b3a089538239896a2714abc2c8c65aa596 --- gst/rtsp-server/rtsp-client.c | 8 -------- gst/rtsp-server/rtsp-stream.c | 5 ----- 2 files changed, 13 deletions(-) diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 05ff274..4f3212d 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -1846,8 +1846,6 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) if (media == NULL) goto media_not_found_no_reply; - /* FIXME-WFD : wfd url problem */ -#if 0 if (path[matched] == '\0') { if (gst_rtsp_media_n_streams (media) == 1) { stream = gst_rtsp_media_get_stream (media, 0); @@ -1863,12 +1861,6 @@ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx) /* find the stream now using the control part */ stream = gst_rtsp_media_find_stream (media, control); } -#else - control = g_strdup ("stream=0"); - - /* find the stream now using the control part */ - stream = gst_rtsp_media_find_stream (media, control); -#endif if (stream == NULL) goto stream_not_found; diff --git a/gst/rtsp-server/rtsp-stream.c b/gst/rtsp-server/rtsp-stream.c index 9805c1e..a015346 100644 --- a/gst/rtsp-server/rtsp-stream.c +++ b/gst/rtsp-server/rtsp-stream.c @@ -1280,15 +1280,10 @@ alloc_ports (GstRTSPStream * stream) G_SOCKET_FAMILY_IPV4, priv->udpsrc_v4, priv->udpsink, &priv->server_port_v4, &priv->server_addr_v4); - /* FIXME-WFD : force to disable ipv6 mode in WFD mode */ -#if 0 priv->have_ipv6 = alloc_ports_one_family (stream, priv->pool, priv->buffer_size, G_SOCKET_FAMILY_IPV6, priv->udpsrc_v6, priv->udpsink, &priv->server_port_v6, &priv->server_addr_v6); -#else - priv->have_ipv6 = FALSE; -#endif return priv->have_ipv4 || priv->have_ipv6; } -- 2.7.4 From 8d671acd29a3420e775d9a255f6142c4b1337d64 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Wed, 16 Dec 2015 10:09:11 +0900 Subject: [PATCH 06/16] Fix dereferencing null pointer problem Change-Id: I2524622ca707ed8b554e6feef3171b29e90732c1 --- gst/rtsp-server/gstwfdmessage.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gst/rtsp-server/gstwfdmessage.c b/gst/rtsp-server/gstwfdmessage.c index b70e57d..3dbd6db 100755 --- a/gst/rtsp-server/gstwfdmessage.c +++ b/gst/rtsp-server/gstwfdmessage.c @@ -1808,7 +1808,7 @@ gst_wfd_message_get_contentprotection_type (GstWFDMessage * msg, g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); if (msg->content_protection && msg->content_protection->hdcp2_spec) { char *result = NULL; - char *ptr = NULL; + char *ptr[2] = {0, }; if (!g_strcmp0 (msg->content_protection->hdcp2_spec->hdcpversion, "none")) { *hdcpversion = GST_WFD_HDCP_NONE; *TCPPort = 0; @@ -1826,11 +1826,15 @@ gst_wfd_message_get_contentprotection_type (GstWFDMessage * msg, return GST_WFD_OK; } - result = strtok_r (msg->content_protection->hdcp2_spec->TCPPort, "=", &ptr); - while (result != NULL) { - result = strtok_r (NULL, "=", &ptr); - *TCPPort = atoi (result); - break; + if (msg->content_protection->hdcp2_spec->TCPPort) { + result = strtok_r (msg->content_protection->hdcp2_spec->TCPPort, "=", &ptr[0]); + while (result != NULL) { + result = strtok_r (NULL, "=", &ptr[1]); + *TCPPort = atoi (result); + break; + } + } else { + *TCPPort = 0; } } else *hdcpversion = GST_WFD_HDCP_NONE; -- 2.7.4 From 814ff75c36a19e6e5a54095309c3591343e3be5b Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Fri, 18 Dec 2015 15:56:02 +0900 Subject: [PATCH 07/16] Fix crash by misusing strtok_r Change-Id: I7f4a4e6728efb4651abe076621d139329b4bbb8d --- gst/rtsp-server/gstwfdmessage.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gst/rtsp-server/gstwfdmessage.c b/gst/rtsp-server/gstwfdmessage.c index 3dbd6db..9aa873e 100755 --- a/gst/rtsp-server/gstwfdmessage.c +++ b/gst/rtsp-server/gstwfdmessage.c @@ -1808,7 +1808,7 @@ gst_wfd_message_get_contentprotection_type (GstWFDMessage * msg, g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL); if (msg->content_protection && msg->content_protection->hdcp2_spec) { char *result = NULL; - char *ptr[2] = {0, }; + char *ptr = NULL; if (!g_strcmp0 (msg->content_protection->hdcp2_spec->hdcpversion, "none")) { *hdcpversion = GST_WFD_HDCP_NONE; *TCPPort = 0; @@ -1827,9 +1827,9 @@ gst_wfd_message_get_contentprotection_type (GstWFDMessage * msg, } if (msg->content_protection->hdcp2_spec->TCPPort) { - result = strtok_r (msg->content_protection->hdcp2_spec->TCPPort, "=", &ptr[0]); - while (result != NULL) { - result = strtok_r (NULL, "=", &ptr[1]); + result = strtok_r (msg->content_protection->hdcp2_spec->TCPPort, "=", &ptr); + while (result != NULL && ptr != NULL) { + result = strtok_r (NULL, "=", &ptr); *TCPPort = atoi (result); break; } -- 2.7.4 From bbf1ce4729d6429add27d772cbc7a3c894e6c0c2 Mon Sep 17 00:00:00 2001 From: Hyunjun Ko Date: Mon, 18 Jan 2016 16:56:39 +0900 Subject: [PATCH 08/16] Add signal to notify when M7 done Change-Id: I56802c4cb7c6fee8407a689d51af4cc443887ef0 --- gst/rtsp-server/rtsp-client-wfd.c | 11 ++++++++++- gst/rtsp-server/rtsp-client-wfd.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c index 38cbae1..8566638 100644 --- a/gst/rtsp-server/rtsp-client-wfd.c +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -148,6 +148,7 @@ enum SIGNAL_WFD_OPTIONS_REQUEST, SIGNAL_WFD_GET_PARAMETER_REQUEST, SIGNAL_WFD_KEEP_ALIVE_FAIL, + SIGNAL_WFD_PLAYING_DONE, SIGNAL_WFD_LAST }; @@ -250,6 +251,11 @@ gst_rtsp_wfd_client_class_init (GstRTSPWFDClientClass * klass) G_STRUCT_OFFSET (GstRTSPWFDClientClass, wfd_keep_alive_fail), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + gst_rtsp_client_wfd_signals[SIGNAL_WFD_PLAYING_DONE] = + g_signal_new ("wfd-playing-done", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstRTSPWFDClientClass, wfd_playing_done), NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + klass->wfd_options_request = wfd_options_request_done; klass->wfd_get_param_request = wfd_get_param_request_done; @@ -944,8 +950,11 @@ handle_wfd_play (GstRTSPClient * client, GstRTSPContext * ctx) g_return_if_fail (priv != NULL); wfd_set_keep_alive_condition(_client); - + priv->stats_timer_id = g_timeout_add (2000, wfd_display_rtp_stats, _client); + + g_signal_emit (client, + gst_rtsp_client_wfd_signals[SIGNAL_WFD_PLAYING_DONE], 0, NULL); } static void diff --git a/gst/rtsp-server/rtsp-client-wfd.h b/gst/rtsp-server/rtsp-client-wfd.h index baf54b9..67faab7 100644 --- a/gst/rtsp-server/rtsp-client-wfd.h +++ b/gst/rtsp-server/rtsp-client-wfd.h @@ -112,6 +112,7 @@ struct _GstRTSPWFDClientClass { void (*wfd_options_request) (GstRTSPWFDClient *client, GstRTSPContext *ctx); void (*wfd_get_param_request) (GstRTSPWFDClient *client, GstRTSPContext *ctx); void (*wfd_keep_alive_fail) (GstRTSPWFDClient *client); + void (*wfd_playing_done) (GstRTSPWFDClient *client); /*< private >*/ gpointer _gst_reserved[GST_PADDING_LARGE]; -- 2.7.4 From 74fe786d124dfdc924d9a0dedc43540820d2dea6 Mon Sep 17 00:00:00 2001 From: "Hyunsoo, Park" Date: Tue, 2 Feb 2016 17:02:00 +0900 Subject: [PATCH 09/16] Add new property to pulsesrc instead of audio device Change-Id: I59a28a8f3b1a39b84e5c55fb559cff3faf77838d Signed-off-by: Hyunsoo, Park --- gst/rtsp-server/rtsp-media-factory-wfd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gst/rtsp-server/rtsp-media-factory-wfd.c b/gst/rtsp-server/rtsp-media-factory-wfd.c index 0b7e76d..8cca5ec 100644 --- a/gst/rtsp-server/rtsp-media-factory-wfd.c +++ b/gst/rtsp-server/rtsp-media-factory-wfd.c @@ -337,6 +337,7 @@ _rtsp_media_factory_wfd_create_audio_capture_bin (GstRTSPMediaFactoryWFD * GstElement *audio_convert = NULL; GstElement *aqueue = NULL; GstRTSPMediaFactoryWFDPrivate *priv = NULL; + GstStructure *audio_properties_name = NULL; guint channels = 0; gboolean is_enc_req = TRUE; @@ -360,7 +361,9 @@ _rtsp_media_factory_wfd_create_audio_capture_bin (GstRTSPMediaFactoryWFD * GST_INFO_OBJECT (factory, "audio_do_timestamp : %d", priv->audio_do_timestamp); - g_object_set (audiosrc, "device", priv->audio_device, NULL); + audio_properties_name = gst_structure_new_from_string(priv->audio_device); + + g_object_set (audiosrc, "stream-properties", audio_properties_name, NULL); g_object_set (audiosrc, "buffer-time", (gint64) priv->audio_buffer_time, NULL); g_object_set (audiosrc, "latency-time", (gint64) priv->audio_latency_time, @@ -499,11 +502,12 @@ _rtsp_media_factory_wfd_create_audio_capture_bin (GstRTSPMediaFactoryWFD * priv->audio_queue = aqueue; if (acodec) g_free (acodec); - + if (gst_structure_free) gst_structure_free(audio_properties_name); return TRUE; create_error: if (acodec) g_free (acodec); + if (gst_structure_free) gst_structure_free(audio_properties_name); return FALSE; } -- 2.7.4 From 0919d4bbaefd448c7cd988da6333639725e0763f Mon Sep 17 00:00:00 2001 From: "Hyunsoo, Park" Date: Thu, 3 Mar 2016 15:10:27 +0900 Subject: [PATCH 10/16] Fix warning of audio device property in free process Change-Id: I41e4e7ab4b6d4d40ef90c65a377342e8efafa2e2 Signed-off-by: Hyunsoo, Park --- gst/rtsp-server/rtsp-media-factory-wfd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst/rtsp-server/rtsp-media-factory-wfd.c b/gst/rtsp-server/rtsp-media-factory-wfd.c index 8cca5ec..60371b1 100644 --- a/gst/rtsp-server/rtsp-media-factory-wfd.c +++ b/gst/rtsp-server/rtsp-media-factory-wfd.c @@ -502,12 +502,12 @@ _rtsp_media_factory_wfd_create_audio_capture_bin (GstRTSPMediaFactoryWFD * priv->audio_queue = aqueue; if (acodec) g_free (acodec); - if (gst_structure_free) gst_structure_free(audio_properties_name); + if (audio_properties_name) gst_structure_free(audio_properties_name); return TRUE; create_error: if (acodec) g_free (acodec); - if (gst_structure_free) gst_structure_free(audio_properties_name); + if (audio_properties_name) gst_structure_free(audio_properties_name); return FALSE; } -- 2.7.4 From 9a09312feff357fc7a13e402765897a028df6063 Mon Sep 17 00:00:00 2001 From: SeokHoon Lee Date: Thu, 28 Apr 2016 11:31:55 +0900 Subject: [PATCH 11/16] modify audio counting algorithm from vd Signed-off-by: SeokHoon Lee Change-Id: I90458b60a0508896d3f2289c9ef7dbfa4a0f9ee1 --- gst/rtsp-server/gstwfdmessage.c | 11 +++++++---- packaging/gst-rtsp-server.spec | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) mode change 100755 => 100644 gst/rtsp-server/gstwfdmessage.c diff --git a/gst/rtsp-server/gstwfdmessage.c b/gst/rtsp-server/gstwfdmessage.c old mode 100755 new mode 100644 index 9aa873e..fc81a9c --- a/gst/rtsp-server/gstwfdmessage.c +++ b/gst/rtsp-server/gstwfdmessage.c @@ -1391,7 +1391,6 @@ gst_wfd_message_set_supported_audio_format (GstWFDMessage * msg, GstWFDAudioFormats a_codec, guint a_freq, guint a_channels, guint a_bitwidth, guint32 a_latency) { - guint temp = a_codec; guint i = 0; guint pcm = 0, aac = 0, ac3 = 0; @@ -1401,10 +1400,14 @@ gst_wfd_message_set_supported_audio_format (GstWFDMessage * msg, msg->audio_codecs = g_new0 (GstWFDAudioCodeclist, 1); if (a_codec != GST_WFD_AUDIO_UNKNOWN) { - while (temp) { + + if (a_codec & GST_WFD_AUDIO_LPCM) msg->audio_codecs->count++; - temp >>= 1; - } + if (a_codec & GST_WFD_AUDIO_AAC) + msg->audio_codecs->count++; + if (a_codec & GST_WFD_AUDIO_AC3) + msg->audio_codecs->count++; + msg->audio_codecs->list = g_new0 (GstWFDAudioCodec, msg->audio_codecs->count); for (; i < msg->audio_codecs->count; i++) { diff --git a/packaging/gst-rtsp-server.spec b/packaging/gst-rtsp-server.spec index 0474352..b0e3187 100644 --- a/packaging/gst-rtsp-server.spec +++ b/packaging/gst-rtsp-server.spec @@ -1,7 +1,7 @@ Name: gst-rtsp-server Summary: Multimedia Framework Library Version: 1.6.1 -Release: 0 +Release: 1 Url: http://gstreamer.freedesktop.org/ Group: System/Libraries License: LGPL-2.0+ -- 2.7.4 From a214249b54e3424b9e0651c1acdb8c390e4e86c2 Mon Sep 17 00:00:00 2001 From: SeokHoon Lee Date: Tue, 3 May 2016 13:08:27 +0900 Subject: [PATCH 12/16] function name change. prepare_request -> gst_prepare_request, send_request -> gst_send_request. Signed-off-by: SeokHoon Lee Change-Id: I5a149f92dc999ace6b14032656ac3760eb5a061b --- gst/rtsp-server/rtsp-client-wfd.c | 31 ++++++++++++------------------- gst/rtsp-server/rtsp-client-wfd.h | 4 ++++ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c index 8566638..6833641 100644 --- a/gst/rtsp-server/rtsp-client-wfd.c +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -187,13 +187,6 @@ static gboolean wfd_configure_client_media (GstRTSPClient * client, GstRTSPMedia GstRTSPResult prepare_trigger_request (GstRTSPWFDClient * client, GstRTSPMessage * request, GstWFDTriggerType trigger_type, gchar * url); -GstRTSPResult prepare_request (GstRTSPWFDClient * client, - GstRTSPMessage * request, GstRTSPMethod method, gchar * url); - -void -send_request (GstRTSPWFDClient * client, GstRTSPSession * session, - GstRTSPMessage * request); - GstRTSPResult prepare_response (GstRTSPWFDClient * client, GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPMethod method); @@ -1752,7 +1745,7 @@ error: } /** -* prepare_request: +* gst_prepare_request: * @client: client object * @request : requst message to be prepared * @method : RTSP method of the request @@ -1765,7 +1758,7 @@ error: * Returns: a #GstRTSPResult. */ GstRTSPResult -prepare_request (GstRTSPWFDClient * client, GstRTSPMessage * request, +gst_prepare_request (GstRTSPWFDClient * client, GstRTSPMessage * request, GstRTSPMethod method, gchar * url) { GstRTSPResult res = GST_RTSP_OK; @@ -2064,7 +2057,7 @@ error: void -send_request (GstRTSPWFDClient * client, GstRTSPSession * session, +gst_send_request (GstRTSPWFDClient * client, GstRTSPSession * session, GstRTSPMessage * request) { GstRTSPResult res = GST_RTSP_OK; @@ -2188,7 +2181,7 @@ handle_M1_message (GstRTSPWFDClient * client) GstRTSPResult res = GST_RTSP_OK; GstRTSPMessage request = { 0 }; - res = prepare_request (client, &request, GST_RTSP_OPTIONS, (gchar *) "*"); + res = gst_prepare_request (client, &request, GST_RTSP_OPTIONS, (gchar *) "*"); if (GST_RTSP_OK != res) { GST_ERROR_OBJECT (client, "Failed to prepare M1 request....\n"); return res; @@ -2196,7 +2189,7 @@ handle_M1_message (GstRTSPWFDClient * client) GST_DEBUG_OBJECT (client, "Sending M1 request.. (OPTIONS request)"); - send_request (client, NULL, &request); + gst_send_request (client, NULL, &request); return res; } @@ -2237,7 +2230,7 @@ handle_M3_message (GstRTSPWFDClient * client) goto error; } - res = prepare_request (client, &request, GST_RTSP_GET_PARAMETER, url_str); + res = gst_prepare_request (client, &request, GST_RTSP_GET_PARAMETER, url_str); if (GST_RTSP_OK != res) { GST_ERROR_OBJECT (client, "Failed to prepare M3 request....\n"); goto error; @@ -2245,7 +2238,7 @@ handle_M3_message (GstRTSPWFDClient * client) GST_DEBUG_OBJECT (client, "Sending GET_PARAMETER request message (M3)..."); - send_request (client, NULL, &request); + gst_send_request (client, NULL, &request); return res; @@ -2279,7 +2272,7 @@ handle_M4_message (GstRTSPWFDClient * client) goto error; } - res = prepare_request (client, &request, GST_RTSP_SET_PARAMETER, url_str); + res = gst_prepare_request (client, &request, GST_RTSP_SET_PARAMETER, url_str); if (GST_RTSP_OK != res) { GST_ERROR_OBJECT (client, "Failed to prepare M4 request....\n"); goto error; @@ -2287,7 +2280,7 @@ handle_M4_message (GstRTSPWFDClient * client) GST_DEBUG_OBJECT (client, "Sending SET_PARAMETER request message (M4)..."); - send_request (client, NULL, &request); + gst_send_request (client, NULL, &request); return res; @@ -2330,7 +2323,7 @@ gst_rtsp_wfd_client_trigger_request (GstRTSPWFDClient * client, GST_DEBUG_OBJECT (client, "Sending trigger request message...: %d", type); - send_request (client, NULL, &request); + gst_send_request (client, NULL, &request); return res; @@ -2409,7 +2402,7 @@ wfd_ckeck_keep_alive_response (gpointer userdata) } /*Sending keep_alive (M16) message. - Without calling prepare_request function.*/ + Without calling gst_prepare_request function.*/ static GstRTSPResult handle_M16_message (GstRTSPWFDClient * client) { @@ -2425,7 +2418,7 @@ handle_M16_message (GstRTSPWFDClient * client) return FALSE; } - send_request (client, NULL, &request); + gst_send_request (client, NULL, &request); return GST_RTSP_OK; } diff --git a/gst/rtsp-server/rtsp-client-wfd.h b/gst/rtsp-server/rtsp-client-wfd.h index 67faab7..f9a836e 100644 --- a/gst/rtsp-server/rtsp-client-wfd.h +++ b/gst/rtsp-server/rtsp-client-wfd.h @@ -135,6 +135,10 @@ GstRTSPResult gst_rtsp_wfd_client_set_video_native_resolution ( GstRTSPWFDClient * client, guint64 native_reso); GstRTSPResult gst_rtsp_wfd_client_set_audio_codec ( GstRTSPWFDClient * client, guint8 audio_codec); +GstRTSPResult gst_prepare_request (GstRTSPWFDClient * client, + GstRTSPMessage * request, GstRTSPMethod method, gchar * url); +void gst_send_request (GstRTSPWFDClient * client, + GstRTSPSession * session, GstRTSPMessage * request); guint gst_rtsp_wfd_client_get_audio_codec(GstRTSPWFDClient *client); guint gst_rtsp_wfd_client_get_audio_freq(GstRTSPWFDClient *client); -- 2.7.4 From f1b4c128effb154b75c3e3e96767c4da29871085 Mon Sep 17 00:00:00 2001 From: "Hyunsoo, Park" Date: Wed, 4 May 2016 11:08:09 +0900 Subject: [PATCH 13/16] Add preventing memory leak code for svace Change-Id: I29a10d9976c8f6f644620a6cb1b486ae6e9a7073 Signed-off-by: Hyunsoo, Park --- gst/rtsp-server/rtsp-client-wfd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c index 6833641..89e4704 100644 --- a/gst/rtsp-server/rtsp-client-wfd.c +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -1161,6 +1161,8 @@ handle_wfd_options_request (GstRTSPClient * client, GstRTSPContext * ctx) gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PUBLIC, str); g_free (str); + g_free (tmp); + str = NULL; res = @@ -1773,6 +1775,9 @@ gst_prepare_request (GstRTSPWFDClient * client, GstRTSPMessage * request, /* initialize the request */ res = gst_rtsp_message_init_request (request, method, url); + if (method == GST_RTSP_GET_PARAMETER || GST_RTSP_SET_PARAMETER) { + g_free(url); + } if (res < 0) { GST_ERROR ("init request failed"); return res; @@ -2138,6 +2143,7 @@ prepare_response (GstRTSPWFDClient * client, GstRTSPMessage * request, gst_rtsp_message_add_header (response, GST_RTSP_HDR_PUBLIC, str); g_free (str); + g_free (tmp); str = NULL; res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_USER_AGENT, @@ -2415,10 +2421,12 @@ handle_M16_message (GstRTSPWFDClient * client) res = gst_rtsp_message_init_request (&request, GST_RTSP_GET_PARAMETER, url_str); if (res < 0) { GST_ERROR ("init request failed"); + g_free(url_str); return FALSE; } gst_send_request (client, NULL, &request); + g_free(url_str); return GST_RTSP_OK; } -- 2.7.4 From c70f704fce9cd59ba229dfbfe80ae5238ce7d929 Mon Sep 17 00:00:00 2001 From: "Hyunsoo, Park" Date: Wed, 11 May 2016 16:20:25 +0900 Subject: [PATCH 14/16] modify if condition of 'GST_GET_PARAMETER' and 'GST_SET_PARAMETER' for g_free(url) Change-Id: If3ddfadd79273c3941f8fc57f3f4a2c82a5f8824 Signed-off-by: Hyunsoo, Park --- gst/rtsp-server/rtsp-client-wfd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c index 89e4704..f6c9f93 100644 --- a/gst/rtsp-server/rtsp-client-wfd.c +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -1775,9 +1775,11 @@ gst_prepare_request (GstRTSPWFDClient * client, GstRTSPMessage * request, /* initialize the request */ res = gst_rtsp_message_init_request (request, method, url); - if (method == GST_RTSP_GET_PARAMETER || GST_RTSP_SET_PARAMETER) { + + if (method == GST_RTSP_GET_PARAMETER || method == GST_RTSP_SET_PARAMETER) { g_free(url); } + if (res < 0) { GST_ERROR ("init request failed"); return res; -- 2.7.4 From bd6d25eb0cf03a3092d96eb9ca5cf3bb5d68d144 Mon Sep 17 00:00:00 2001 From: SeokHoon Lee Date: Mon, 9 May 2016 10:16:27 +0900 Subject: [PATCH 15/16] Add new function for hdcp Signed-off-by: SeokHoon Lee Change-Id: I321e6eccf25498c74b1222dfb3cbfcd894efb920 --- gst/rtsp-server/rtsp-client-wfd.c | 18 ++++++++++++++++++ gst/rtsp-server/rtsp-client-wfd.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/gst/rtsp-server/rtsp-client-wfd.c b/gst/rtsp-server/rtsp-client-wfd.c index 6833641..9d6dbae 100644 --- a/gst/rtsp-server/rtsp-client-wfd.c +++ b/gst/rtsp-server/rtsp-client-wfd.c @@ -2760,6 +2760,24 @@ gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean en priv->protection_enabled = enable; } +void +gst_rtsp_wfd_client_set_hdcp_version(GstRTSPWFDClient *client, GstWFDHDCPProtection version) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->hdcp_version = version; +} + +void +gst_rtsp_wfd_client_set_hdcp_port(GstRTSPWFDClient *client, guint32 port) +{ + GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); + g_return_if_fail (priv != NULL); + + priv->hdcp_tcpport = port; +} + void gst_rtsp_wfd_client_set_keep_alive_flag(GstRTSPWFDClient *client, gboolean flag) { GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client); diff --git a/gst/rtsp-server/rtsp-client-wfd.h b/gst/rtsp-server/rtsp-client-wfd.h index f9a836e..5cbf650 100644 --- a/gst/rtsp-server/rtsp-client-wfd.h +++ b/gst/rtsp-server/rtsp-client-wfd.h @@ -172,6 +172,8 @@ void gst_rtsp_wfd_client_set_edid_supported(GstRTSPWFDClient *client, gboolean s void gst_rtsp_wfd_client_set_edid_hresolution(GstRTSPWFDClient *client, guint32 reso); void gst_rtsp_wfd_client_set_edid_vresolution(GstRTSPWFDClient *client, guint32 reso); void gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean enable); +void gst_rtsp_wfd_client_set_hdcp_version(GstRTSPWFDClient *client, GstWFDHDCPProtection version); +void gst_rtsp_wfd_client_set_hdcp_port(GstRTSPWFDClient *client, guint32 port); void gst_rtsp_wfd_client_set_keep_alive_flag(GstRTSPWFDClient *client, gboolean flag); void gst_rtsp_wfd_client_set_aud_codec(GstRTSPWFDClient *client, guint acodec); void gst_rtsp_wfd_client_set_audio_channels(GstRTSPWFDClient *client, guint channels); -- 2.7.4 From 454ddee8481bf499e91a3597a79421a7f98522fe Mon Sep 17 00:00:00 2001 From: SeokHoon Lee Date: Fri, 27 May 2016 09:36:43 +0900 Subject: [PATCH 16/16] change function name - set_target_state -> gst_rtsp_media_set_target_state - delete static in function gst_rtsp_media_set_target_state, set_rtsp_media_set_status Signed-off-by: SeokHoon Lee Change-Id: I681092d03e9071831e5b0010818457c68b1446b1 --- gst/rtsp-server/rtsp-media.c | 16 ++++++++-------- gst/rtsp-server/rtsp-media.h | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index 5a6f758..0b8c821 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -1811,7 +1811,7 @@ media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked) g_ptr_array_foreach (priv->streams, (GFunc) stream_update_blocked, media); } -static void +void gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status) { GstRTSPMediaPrivate *priv = media->priv; @@ -2061,8 +2061,8 @@ set_state (GstRTSPMedia * media, GstState state) return ret; } -static GstStateChangeReturn -set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state) +GstStateChangeReturn +gst_rtsp_media_set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state) { GstRTSPMediaPrivate *priv = media->priv; GstStateChangeReturn ret; @@ -2401,7 +2401,7 @@ start_preroll (GstRTSPMedia * media) GST_INFO ("setting pipeline to PAUSED for media %p", media); /* first go to PAUSED */ - ret = set_target_state (media, GST_STATE_PAUSED, TRUE); + ret = gst_rtsp_media_set_target_state (media, GST_STATE_PAUSED, TRUE); switch (ret) { case GST_STATE_CHANGE_SUCCESS: @@ -2875,7 +2875,7 @@ gst_rtsp_media_unprepare (GstRTSPMedia * media) GST_INFO ("unprepare media %p", media); if (priv->blocked) media_streams_set_blocked (media, FALSE); - set_target_state (media, GST_STATE_NULL, FALSE); + gst_rtsp_media_set_target_state (media, GST_STATE_NULL, FALSE); success = TRUE; if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) { @@ -3722,14 +3722,14 @@ default_suspend (GstRTSPMedia * media) break; case GST_RTSP_SUSPEND_MODE_PAUSE: GST_DEBUG ("media %p suspend to PAUSED", media); - ret = set_target_state (media, GST_STATE_PAUSED, TRUE); + ret = gst_rtsp_media_set_target_state (media, GST_STATE_PAUSED, TRUE); if (ret == GST_STATE_CHANGE_FAILURE) goto state_failed; unblock = TRUE; break; case GST_RTSP_SUSPEND_MODE_RESET: GST_DEBUG ("media %p suspend to NULL", media); - ret = set_target_state (media, GST_STATE_NULL, TRUE); + ret = gst_rtsp_media_set_target_state (media, GST_STATE_NULL, TRUE); if (ret == GST_STATE_CHANGE_FAILURE) goto state_failed; /* Because payloader needs to set the sequence number as @@ -3912,7 +3912,7 @@ media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) gst_rtsp_media_unprepare (media); } else { GST_INFO ("state %s media %p", gst_element_state_get_name (state), media); - set_target_state (media, state, FALSE); + gst_rtsp_media_set_target_state (media, state, FALSE); /* when we are buffering, don't update the state yet, this will be done * when buffering finishes */ if (priv->buffering) { diff --git a/gst/rtsp-server/rtsp-media.h b/gst/rtsp-server/rtsp-media.h index f1be13c..10988f8 100644 --- a/gst/rtsp-server/rtsp-media.h +++ b/gst/rtsp-server/rtsp-media.h @@ -257,6 +257,8 @@ gboolean gst_rtsp_media_set_state (GstRTSPMedia *media, GstS GPtrArray *transports); void gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state); +GstStateChangeReturn gst_rtsp_media_set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state); +void gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status); G_END_DECLS -- 2.7.4