--- /dev/null
-include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml"
++include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/1.16/gitlab/ci_template.yml"
--- /dev/null
+ <Project
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
+ xmlns="http://usefulinc.com/ns/doap#"
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
+ xmlns:admin="http://webns.net/mvcb/">
+
+ <name>GStreamer RTSP Server</name>
+ <shortname>gst-rtsp-server</shortname>
+ <homepage rdf:resource="http://gstreamer.freedesktop.org/modules/gst-rtsp-server.html" />
+ <created>1999-10-31</created>
+ <shortdesc xml:lang="en">
+ RTSP server library based on GStreamer
+ </shortdesc>
+ <description xml:lang="en">
+ RTSP server library based on GStreamer
+ </description>
+ <category></category>
+ <bug-database rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/" />
+ <screenshots></screenshots>
+ <mailing-list rdf:resource="http://lists.freedesktop.org/mailman/listinfo/gstreamer-devel" />
+ <programming-language>C</programming-language>
+ <license rdf:resource="http://usefulinc.com/doap/licenses/lgpl" />
+ <download-page rdf:resource="http://gstreamer.freedesktop.org/download/" />
+
+ <repository>
+ <GitRepository>
+ <location rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server"/>
+ <browse rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server"/>
+ </GitRepository>
+ </repository>
+
+ <release>
+ <Version>
+ <revision>1.19.2</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2021-09-23</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.19.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.19.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2021-06-01</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.19.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.18.0</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2020-09-08</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.18.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.17.90</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2020-08-20</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.17.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.17.2</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2020-07-03</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.17.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.17.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2020-06-19</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.17.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.16.0</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2019-04-19</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.16.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.15.90</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2019-04-11</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.15.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.15.2</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2019-02-26</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.15.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.15.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2019-01-17</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.15.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.14.0</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2018-03-19</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.14.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.13.91</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2018-03-13</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.13.91.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.13.90</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2018-03-03</created>
+ <file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.13.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.13.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2018-02-15</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.13.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.12.4</revision>
+ <branch>1.12</branch>
+ <name></name>
+ <created>2017-12-07</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.4.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.12.3</revision>
+ <branch>1.12</branch>
+ <name></name>
+ <created>2017-09-18</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.3.tar.xz" />
+ </Version>
+ </release>
+
++ <release>
++ <Version>
++ <revision>1.12.2</revision>
++ <branch>1.12</branch>
++ <name></name>
++ <created>2017-07-14</created>
++ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.2.tar.xz" />
++ </Version>
++ </release>
++
++ <release>
++ <Version>
++ <revision>1.12.1</revision>
++ <branch>1.12</branch>
++ <name></name>
++ <created>2017-06-20</created>
++ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.1.tar.xz" />
++ </Version>
++ </release>
++
+ <release>
+ <Version>
+ <revision>1.12.2</revision>
+ <branch>1.12</branch>
+ <name></name>
+ <created>2017-07-14</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.12.1</revision>
+ <branch>1.12</branch>
+ <name></name>
+ <created>2017-06-20</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.12.0</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2017-05-04</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.12.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.11.91</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2017-04-27</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.91.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.11.90</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2017-04-07</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.11.2</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2017-02-24</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.11.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2017-01-12</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.11.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.10.0</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-11-01</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.10.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.9.90</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-09-30</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.9.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.9.2</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-09-01</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.9.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.9.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-06-06</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.9.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.8.0</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-03-24</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.8.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.7.91</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-03-15</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.91.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.7.90</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-03-01</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.7.2</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2016-02-19</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.7.1</revision>
+ <branch>master</branch>
+ <name></name>
+ <created>2015-12-24</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.7.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.6.2</revision>
+ <branch>1.6</branch>
+ <name></name>
+ <created>2015-12-14</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.6.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.6.1</revision>
+ <branch>1.6</branch>
+ <name></name>
+ <created>2015-10-30</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.6.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.6.0</revision>
+ <branch>1.6</branch>
+ <name></name>
+ <created>2015-09-25</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.6.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.5.91</revision>
+ <branch>1.5</branch>
+ <name></name>
+ <created>2015-09-18</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.91.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.5.90</revision>
+ <branch>1.5</branch>
+ <name></name>
+ <created>2015-08-19</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.5.2</revision>
+ <branch>1.5</branch>
+ <name></name>
+ <created>2015-06-24</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.5.1</revision>
+ <branch>1.5</branch>
+ <name></name>
+ <created>2015-06-07</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.5.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.4.0</revision>
+ <branch>1.4</branch>
+ <name></name>
+ <created>2014-07-19</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.4.0.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.3.91</revision>
+ <branch>1.3</branch>
+ <name></name>
+ <created>2014-07-11</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.91.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.3.90</revision>
+ <branch>1.3</branch>
+ <name></name>
+ <created>2014-06-28</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.90.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.3.3</revision>
+ <branch>1.3</branch>
+ <name></name>
+ <created>2014-06-22</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.3.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.3.2</revision>
+ <branch>1.3</branch>
+ <name></name>
+ <created>2014-05-21</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.2.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.3.1</revision>
+ <branch>1.3</branch>
+ <name></name>
+ <created>2014-05-03</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.3.1.tar.xz" />
+ </Version>
+ </release>
+
+ <release>
+ <Version>
+ <revision>1.1.90</revision>
+ <branch>1.1</branch>
+ <name></name>
+ <created>2014-02-09</created>
+ <file-release rdf:resource="http://gstreamer.freedesktop.org/src/gst-rtsp-server/gst-rtsp-server-1.1.90.tar.xz" />
+ </Version>
+ </release>
+
+ <maintainer>
+ <foaf:Person>
+ <foaf:name>Wim Taymans</foaf:name>
+ <foaf:mbox_sha1sum>0d93fde052812d51a05fd86de9bdbf674423daa2</foaf:mbox_sha1sum>
+ </foaf:Person>
+ </maintainer>
+
+ </Project>
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com>
++ *
++ * 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
++ *
++ * <refsect2>
++ * <para>
++ * The GstWFDMessage helper functions makes it easy to parse and create WFD
++ * messages.
++ * </para>
++ * </refsect2>
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++//#include <gio/gio.h>
++
++#include "gstwfdmessage-ext.h"
++
++#define FREE_STRING(field) do { g_free (field); (field) = NULL; } while(0)
++
++G_DEFINE_BOXED_TYPE (GstWFDExtMessage, gst_wfd_ext_message, NULL, NULL);
++
++/**
++ * gst_wfd_ext_message_new:
++ * @msg: (out) (transfer full): pointer to new #GstWFDExtMessage
++ *
++ * Allocate a new GstWFDExtMessage and store the result in @msg.
++ *
++ * Returns: a #GstWFDResult.
++ */
++GstWFDResult
++gst_wfd_ext_message_new (GstWFDExtMessage ** msg)
++{
++ GstWFDExtMessage *newmsg;
++
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ newmsg = g_new0 (GstWFDExtMessage, 1);
++
++ *msg = newmsg;
++
++ return gst_wfd_ext_message_init (newmsg);
++}
++
++/**
++ * gst_wfd_ext_message_init:
++ * @msg: a #GstWFDExtMessage
++ *
++ * Initialize @msg so that its contents are as if it was freshly allocated
++ * with gst_wfd_ext_message_new(). This function is mostly used to initialize a message
++ * allocated on the stack. gst_wfd_ext_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_ext_message_init (GstWFDExtMessage * msg)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ return GST_WFD_OK;
++}
++
++/**
++ * gst_wfd_ext_message_uninit:
++ * @msg: a #GstWFDExtMessage
++ *
++ * 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_ext_message_init().
++ *
++ * Returns: a #GstWFDResult.
++ */
++GstWFDResult
++gst_wfd_ext_message_uninit (GstWFDExtMessage * msg)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (msg->tizen_retransmission)
++ FREE_STRING (msg->tizen_retransmission);
++
++ if (msg->tizen_fec)
++ FREE_STRING (msg->tizen_fec);
++
++ if (msg->tizen_latency_mode)
++ FREE_STRING (msg->tizen_latency_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, GstWFDExtMessage * 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_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_TIZEN_WFD_RESTRANSMISSION)) {
++ msg->tizen_retransmission = g_new0 (GstWFDTizenRetransmission, 1);
++ if (strlen (v)) {
++ if (!strstr (v, "none")) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->tizen_retransmission->rtp_port);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->tizen_retransmission->rtcp_port);
++ }
++ }
++ }
++ else if (!g_strcmp0 (attr, GST_STRING_TIZEN_WFD_FEC)) {
++ msg->tizen_fec = g_new0 (GstWFDTizenFec, 1);
++ if (strlen (v)) {
++ if (!strstr (v, "none")) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->tizen_fec->t_max);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->tizen_fec->p_max);
++ }
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_TIZEN_WFD_LATENCY_MODE)) {
++ msg->tizen_latency_mode = g_new0 (GstWFDTizenLatencyMode, 1);
++ if (strlen (v)) {
++ if (strstr (v, GST_STRING_TIZEN_WFD_LATENCY_LOW)) {
++ msg->tizen_latency_mode->latency_mode = GST_WFD_TIZEN_LATENCY_LOW;
++ } else if (strstr (v, GST_STRING_TIZEN_WFD_LATENCY_MID)) {
++ msg->tizen_latency_mode->latency_mode = GST_WFD_TIZEN_LATENCY_MID;
++ } else if (strstr (v, GST_STRING_TIZEN_WFD_LATENCY_HIGH)) {
++ msg->tizen_latency_mode->latency_mode = GST_WFD_TIZEN_LATENCY_HIGH;
++ } else {
++ msg->tizen_latency_mode->latency_mode = GST_WFD_TIZEN_LATENCY_NONE;
++ }
++ }
++ }
++
++ return;
++}
++
++/**
++ * gst_wfd_ext_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_ext_message_parse_buffer (const guint8 * data, guint size,
++ GstWFDExtMessage * 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_ext_message_free:
++ * @msg: a #GstWFDExtMessage
++ *
++ * 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_ext_message_new().
++ *
++ * Returns: a #GstWFDResult.
++ */
++GstWFDResult
++gst_wfd_ext_message_free (GstWFDExtMessage * msg)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ gst_wfd_ext_message_uninit (msg);
++ g_free (msg);
++
++ return GST_WFD_OK;
++}
++
++/**
++ * gst_wfd_ext_message_as_text:
++ * @msg: a #GstWFDExtMessage
++ *
++ * Convert the contents of @msg to a text string.
++ *
++ * Returns: A dynamically allocated string representing the WFD description.
++ */
++gchar *
++gst_wfd_ext_message_as_text (const GstWFDExtMessage * msg)
++{
++ /* change all vars so they match rfc? */
++ GString *lines;
++
++ g_return_val_if_fail (msg != NULL, NULL);
++
++ lines = g_string_new ("");
++
++ if (msg->tizen_retransmission) {
++ g_string_append_printf (lines, GST_STRING_TIZEN_WFD_RESTRANSMISSION);
++ g_string_append_printf (lines, ":");
++ g_string_append_printf (lines, " %d", msg->tizen_retransmission->rtp_port);
++ g_string_append_printf (lines, " %d", msg->tizen_retransmission->rtcp_port);
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ if (msg->tizen_fec) {
++ g_string_append_printf (lines, GST_STRING_TIZEN_WFD_FEC);
++ g_string_append_printf (lines, ":");
++ g_string_append_printf (lines, " %d", msg->tizen_fec->t_max);
++ g_string_append_printf (lines, " %d", msg->tizen_fec->p_max);
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ if (msg->tizen_latency_mode) {
++ g_string_append_printf (lines, GST_STRING_TIZEN_WFD_LATENCY_MODE);
++ g_string_append_printf (lines, ":");
++
++ if (msg->tizen_latency_mode->latency_mode == GST_WFD_TIZEN_LATENCY_LOW)
++ g_string_append_printf (lines, " " GST_STRING_TIZEN_WFD_LATENCY_LOW);
++ else if (msg->tizen_latency_mode->latency_mode == GST_WFD_TIZEN_LATENCY_MID)
++ g_string_append_printf (lines, " " GST_STRING_TIZEN_WFD_LATENCY_MID);
++ else if (msg->tizen_latency_mode->latency_mode ==
++ GST_WFD_TIZEN_LATENCY_HIGH)
++ g_string_append_printf (lines, " " GST_STRING_TIZEN_WFD_LATENCY_HIGH);
++ else
++ g_string_append_printf (lines, " none");
++
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ return g_string_free (lines, FALSE);
++}
++
++gchar *
++gst_wfd_ext_message_param_names_as_text (const GstWFDExtMessage * msg)
++{
++ /* change all vars so they match rfc? */
++ GString *lines;
++ g_return_val_if_fail (msg != NULL, NULL);
++
++ lines = g_string_new ("");
++
++ if (msg->tizen_retransmission) {
++ g_string_append_printf (lines, GST_STRING_TIZEN_WFD_RESTRANSMISSION);
++ g_string_append_printf (lines, "\r\n");
++ }
++ if (msg->tizen_fec) {
++ g_string_append_printf (lines, GST_STRING_TIZEN_WFD_FEC);
++ g_string_append_printf (lines, "\r\n");
++ }
++ if (msg->tizen_latency_mode) {
++ g_string_append_printf (lines, GST_STRING_TIZEN_WFD_LATENCY_MODE);
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ return g_string_free (lines, FALSE);
++}
++
++/**
++ * gst_wfd_ext_message_dump:
++ * @msg: a #GstWFDExtMessage
++ *
++ * Dump the parsed contents of @msg to stdout.
++ *
++ * Returns: a #GstWFDResult.
++ */
++GstWFDResult
++gst_wfd_ext_message_dump (const GstWFDExtMessage * msg)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (msg->tizen_retransmission) {
++ g_print ("tizen_wfd_retransmission: %d %d",
++ msg->tizen_retransmission->rtp_port,
++ msg->tizen_retransmission->rtcp_port);
++ g_print ("\r\n");
++ }
++
++ if (msg->tizen_fec) {
++ g_print ("tizen_wfd_fec: %d %d", msg->tizen_fec->t_max, msg->tizen_fec->p_max);
++ g_print ("\r\n");
++ }
++
++ if (msg->tizen_latency_mode) {
++ g_print ("tizen_wfd_latency_mode:");
++
++ if (msg->tizen_latency_mode->latency_mode == GST_WFD_TIZEN_LATENCY_LOW)
++ g_print (" low");
++ else if (msg->tizen_latency_mode->latency_mode == GST_WFD_TIZEN_LATENCY_MID)
++ g_print (" mid");
++ else if (msg->tizen_latency_mode->latency_mode == GST_WFD_TIZEN_LATENCY_HIGH)
++ g_print (" high");
++ else
++ g_print (" none");
++
++ g_print ("\r\n");
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_ext_message_set_tizen_retransmission (GstWFDExtMessage * msg,
++ guint rtp_port, guint rtcp_port)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (!msg->tizen_retransmission)
++ msg->tizen_retransmission = g_new0 (GstWFDTizenRetransmission, 1);
++
++ msg->tizen_retransmission->rtp_port = rtp_port;
++ msg->tizen_retransmission->rtcp_port = rtcp_port;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_ext_message_get_tizen_retransmission (GstWFDExtMessage * msg,
++ guint * rtp_port, guint * rtcp_port)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (msg->tizen_retransmission != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (rtp_port != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (rtcp_port != NULL, GST_WFD_EINVAL);
++
++ *rtp_port = msg->tizen_retransmission->rtp_port;
++ *rtcp_port = msg->tizen_retransmission->rtcp_port;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_ext_message_set_tizen_fec (GstWFDExtMessage * msg, guint t_max,
++ guint p_max)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (!msg->tizen_fec)
++ msg->tizen_fec = g_new0 (GstWFDTizenFec, 1);
++
++ msg->tizen_fec->t_max = t_max;
++ msg->tizen_fec->p_max = p_max;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_ext_message_get_tizen_fec (GstWFDExtMessage * msg, guint * t_max,
++ guint * p_max)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (msg->tizen_fec != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (t_max != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (p_max != NULL, GST_WFD_EINVAL);
++
++ *t_max = msg->tizen_fec->t_max;
++ *p_max = msg->tizen_fec->p_max;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_ext_message_set_tizen_latency_mode (GstWFDExtMessage * msg,
++ guint latency_mode)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (!msg->tizen_latency_mode)
++ msg->tizen_latency_mode = g_new0 (GstWFDTizenLatencyMode, 1);
++
++ msg->tizen_latency_mode->latency_mode = latency_mode;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_ext_message_get_tizen_latency_mode (GstWFDExtMessage * msg,
++ guint * latency_mode)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (msg->tizen_latency_mode != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (latency_mode != NULL, GST_WFD_EINVAL);
++
++ *latency_mode = msg->tizen_latency_mode->latency_mode;
++
++ return GST_WFD_OK;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com>
++ *
++ * 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_EXT_MESSAGE_H__
++#define __GST_WFD_EXT_MESSAGE_H__
++
++#include <glib.h>
++#include "gstwfdmessage.h"
++
++G_BEGIN_DECLS
++
++#define GST_STRING_TIZEN_WFD_RESTRANSMISSION "tizen_wfd_retransmission"
++#define GST_STRING_TIZEN_WFD_FEC "tizen_wfd_fec"
++#define GST_STRING_TIZEN_WFD_LATENCY_MODE "tizen_wfd_latency_mode"
++#define GST_STRING_TIZEN_WFD_LATENCY_LOW "low"
++#define GST_STRING_TIZEN_WFD_LATENCY_MID "mid"
++#define GST_STRING_TIZEN_WFD_LATENCY_HIGH "high"
++
++typedef enum {
++ GST_WFD_TIZEN_LATENCY_NONE,
++ GST_WFD_TIZEN_LATENCY_LOW,
++ GST_WFD_TIZEN_LATENCY_MID,
++ GST_WFD_TIZEN_LATENCY_HIGH
++} GstWFDTizenLatencyEnum;
++
++typedef struct {
++ guint rtp_port;
++ guint rtcp_port;
++} GstWFDTizenRetransmission;
++
++typedef struct {
++ guint t_max; /*TMAX is the maximum number of source symbols in a block*/
++ guint p_max; /*PMAX is the maximum number of parity symbols in a block*/
++} GstWFDTizenFec;
++
++typedef struct {
++ guint latency_mode;
++} GstWFDTizenLatencyMode;
++
++typedef struct {
++ GstWFDTizenRetransmission *tizen_retransmission;
++ GstWFDTizenFec *tizen_fec;
++ GstWFDTizenLatencyMode *tizen_latency_mode;
++} GstWFDExtMessage;
++
++
++GST_RTSP_SERVER_API
++GType gst_wfd_ext_message_get_type (void);
++
++#define GST_TYPE_WFD_EXT_MESSAGE (gst_wfd_ext_message_get_type())
++#define GST_WFD_EXT_MESSAGE_CAST(object) ((GstWFDExtMessage *)(object))
++#define GST_WFD_EXT_MESSAGE(object) (GST_WFD_EXT_MESSAGE_CAST(object))
++
++/* Session descriptions */
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_new (GstWFDExtMessage **msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_init (GstWFDExtMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_uninit (GstWFDExtMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_free (GstWFDExtMessage *msg);
++
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_parse_buffer (const guint8 *data, guint size, GstWFDExtMessage *msg);
++
++GST_RTSP_SERVER_API
++gchar* gst_wfd_ext_message_as_text (const GstWFDExtMessage *msg);
++
++GST_RTSP_SERVER_API
++gchar* gst_wfd_ext_message_param_names_as_text (const GstWFDExtMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_dump (const GstWFDExtMessage *msg);
++
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_set_tizen_retransmission (GstWFDExtMessage *msg,
++ guint rtp_port,
++ guint rtcp_port);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_get_tizen_retransmission (GstWFDExtMessage *msg,
++ guint *rtp_port,
++ guint *rtcp_port);
++
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_set_tizen_fec (GstWFDExtMessage *msg,
++ guint t_max,
++ guint p_max);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_get_tizen_fec (GstWFDExtMessage *msg,
++ guint *t_max,
++ guint *p_max);
++
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_set_tizen_latency_mode (GstWFDExtMessage *msg,
++ guint latency_mode);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_ext_message_get_tizen_latency_mode (GstWFDExtMessage *msg,
++ guint *latency_mode);
++
++G_END_DECLS
++
++#endif /* __GST_WFD_EXT_MESSAGE_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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
++ *
++ * <refsect2>
++ * <para>
++ * The GstWFDMessage helper functions makes it easy to parse and create WFD
++ * messages.
++ * </para>
++ * </refsect2>
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include <gio/gio.h>
++
++#include "gstwfdmessage.h"
++
++#define EDID_BLOCK_SIZE 128
++#define EDID_BLOCK_COUNT_MAX_SIZE 256
++#define MAX_PORT_SIZE 65535
++
++#define FREE_STRING(field) do { g_free (field); (field) = NULL; } while(0)
++#define REPLACE_STRING(field, val) do { FREE_STRING(field); (field) = g_strdup (val); } while(0)
++
++#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->wfd2_audio_codecs) {
++ guint i = 0;
++ if (msg->wfd2_audio_codecs->list) {
++ for (; i < msg->wfd2_audio_codecs->count; i++) {
++ FREE_STRING(msg->wfd2_audio_codecs->list[i].audio_format);
++ msg->wfd2_audio_codecs->list[i].modes = 0;
++ msg->wfd2_audio_codecs->list[i].latency = 0;
++ }
++ FREE_STRING(msg->wfd2_audio_codecs->list);
++ }
++ FREE_STRING(msg->wfd2_audio_codecs);
++ }
++
++ if (msg->direct_video_formats) {
++ FREE_STRING(msg->direct_video_formats->list);
++ FREE_STRING(msg->direct_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);
++
++ if (msg->direct_mode)
++ FREE_STRING(msg->direct_mode);
++
++ if (msg->tcp_ports)
++ FREE_STRING(msg->tcp_ports);
++
++ if (msg->buf_len)
++ FREE_STRING(msg->buf_len);
++
++ if (msg->audio_status)
++ FREE_STRING(msg->audio_status);
++
++ if (msg->video_status)
++ FREE_STRING(msg->video_status);
++
++ 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)
++#define WFD_READ_UINT64_DIGIT(field) _read_string_space_ended (temp, sizeof (temp), v); v+=strlen(temp); field = strtoull (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)) {
++ WFD_SKIP_SPACE (v);
++ if (strncmp (v, "none", 4)) {
++ 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 {
++ msg->audio_codecs->count = 0;
++ msg->audio_codecs->list = NULL;
++ }
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD_VIDEO_FORMATS)) {
++ msg->video_formats = g_new0 (GstWFDVideoCodeclist, 1);
++ if (strlen (v)) {
++ WFD_SKIP_SPACE (v);
++ if (strncmp (v, "none", 4)) {
++ msg->video_formats->count = 1;
++ msg->video_formats->list = g_new0 (GstWFDVideoCodec, 1);
++ 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 {
++ msg->video_formats->count = 0;
++ msg->video_formats->list = NULL;
++ }
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_AUDIO_CODECS)) {
++ msg->wfd2_audio_codecs = g_new0 (GstWFD2AudioCodeclist, 1);
++ if (strlen (v)) {
++ guint i = 0;
++ msg->wfd2_audio_codecs->count = strlen (v) / 16;
++ msg->wfd2_audio_codecs->list =
++ g_new0 (GstWFDAudioCodec, msg->wfd2_audio_codecs->count);
++ for (; i < msg->wfd2_audio_codecs->count; i++) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_STRING (msg->wfd2_audio_codecs->list[i].audio_format);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->wfd2_audio_codecs->list[i].modes);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->wfd2_audio_codecs->list[i].latency);
++ WFD_SKIP_COMMA (v);
++ }
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_VIDEO_FORMATS)) {
++ msg->direct_video_formats = g_new0 (GstWFD2VideoCodeclist, 1);
++ if (strlen (v)) {
++ msg->direct_video_formats->count = 1;
++ msg->direct_video_formats->list = g_new0 (GstWFDVideoCodec, 1);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->native);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->
++ list->preferred_display_mode_supported);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.profile);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.level);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.CEA_Support);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.VESA_Support);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.HH_Support);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.latency);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.min_slice_size);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.slice_enc_params);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.
++ misc_params.frame_rate_control_support);
++ WFD_SKIP_SPACE (v);
++ if (msg->direct_video_formats->list->preferred_display_mode_supported == 1) {
++ WFD_READ_UINT32 (msg->direct_video_formats->list->H264_codec.max_hres);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32 (msg->direct_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 (GstWFDCoupledSinkCap, 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;
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_DIRECT_STREAMING_MODE)) {
++ msg->direct_mode = g_new0 (GstWFD2DirectStreamingMode, 1);
++ if (strlen (v)) {
++ WFD_SKIP_SPACE (v);
++ if (!g_strcmp0 (v, "active"))
++ msg->direct_mode->direct_mode = TRUE;
++ else
++ msg->direct_mode->direct_mode = FALSE;
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_TRANSPORT_SWITCH)) {
++ msg->tcp_ports = g_new0 (GstWFDTCPPorts, 1);
++ if (strlen (v)) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_STRING (msg->tcp_ports->profile);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->tcp_ports->rtp_port0);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->tcp_ports->rtp_port1);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_STRING (msg->tcp_ports->mode);
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_BUFFER_LEN)) {
++ msg->buf_len = g_new0 (GstWFDBufferLen, 1);
++ if (strlen (v)) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->buf_len->buf_len);
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_AUDIO_STATUS)) {
++ msg->audio_status = g_new0 (GstWFDAudioReport, 1);
++ if (strlen (v)) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->audio_status->aud_bufsize);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT64_DIGIT (msg->audio_status->aud_pts);
++ }
++ } else if (!g_strcmp0 (attr, GST_STRING_WFD2_VIDEO_STATUS)) {
++ msg->video_status = g_new0 (GstWFDVideoReport, 1);
++ if (strlen (v)) {
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT32_DIGIT (msg->video_status->vid_bufsize);
++ WFD_SKIP_SPACE (v);
++ WFD_READ_UINT64_DIGIT (msg->video_status->vid_pts);
++ }
++ }
++ 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);
++ msg = NULL;
++
++ 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);
++ g_string_append_printf (lines, ":");
++ if (msg->audio_codecs->list) {
++ 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, ",");
++ }
++ } else {
++ g_string_append_printf (lines, " none");
++ }
++ 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, ":");
++ if (msg->video_formats->list) {
++ 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");
++ } else {
++ g_string_append_printf (lines, " none");
++ }
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ /* list of wfd2 audio codecs */
++ if (msg->wfd2_audio_codecs) {
++ g_string_append_printf (lines, GST_STRING_WFD2_AUDIO_CODECS);
++ if (msg->wfd2_audio_codecs->list) {
++ g_string_append_printf (lines, ":");
++ for (i = 0; i < msg->wfd2_audio_codecs->count; i++) {
++ g_string_append_printf (lines, " %s",
++ msg->wfd2_audio_codecs->list[i].audio_format);
++ g_string_append_printf (lines, " %08x",
++ msg->wfd2_audio_codecs->list[i].modes);
++ g_string_append_printf (lines, " %02x",
++ msg->wfd2_audio_codecs->list[i].latency);
++ if ((i + 1) < msg->wfd2_audio_codecs->count)
++ g_string_append_printf (lines, ",");
++ }
++ }
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ /* list of direct video codecs */
++ if (msg->direct_video_formats) {
++ g_string_append_printf (lines, GST_STRING_WFD2_VIDEO_FORMATS);
++ if (msg->direct_video_formats->list) {
++ g_string_append_printf (lines, ":");
++ g_string_append_printf (lines, " %02x", msg->direct_video_formats->list->native);
++ g_string_append_printf (lines, " %02x",
++ msg->direct_video_formats->list->preferred_display_mode_supported);
++ g_string_append_printf (lines, " %02x",
++ msg->direct_video_formats->list->H264_codec.profile);
++ g_string_append_printf (lines, " %02x",
++ msg->direct_video_formats->list->H264_codec.level);
++ g_string_append_printf (lines, " %08x",
++ msg->direct_video_formats->list->H264_codec.misc_params.CEA_Support);
++ g_string_append_printf (lines, " %08x",
++ msg->direct_video_formats->list->H264_codec.misc_params.VESA_Support);
++ g_string_append_printf (lines, " %08x",
++ msg->direct_video_formats->list->H264_codec.misc_params.HH_Support);
++ g_string_append_printf (lines, " %02x",
++ msg->direct_video_formats->list->H264_codec.misc_params.latency);
++ g_string_append_printf (lines, " %04x",
++ msg->direct_video_formats->list->H264_codec.misc_params.min_slice_size);
++ g_string_append_printf (lines, " %04x",
++ msg->direct_video_formats->list->H264_codec.misc_params.slice_enc_params);
++ g_string_append_printf (lines, " %02x",
++ msg->direct_video_formats->list->H264_codec.
++ misc_params.frame_rate_control_support);
++
++ if (msg->direct_video_formats->list->H264_codec.max_hres)
++ g_string_append_printf (lines, " %04x",
++ msg->direct_video_formats->list->H264_codec.max_hres);
++ else
++ g_string_append_printf (lines, " none");
++
++ if (msg->direct_video_formats->list->H264_codec.max_vres)
++ g_string_append_printf (lines, " %04x",
++ msg->direct_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 &&
++ (gboolean)msg->coupled_sink->coupled_sink_cap->sink_supported == TRUE) {
++ 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");
++ }
++
++ if (msg->direct_mode && msg->direct_mode->direct_mode) {
++ g_string_append_printf (lines, GST_STRING_WFD2_DIRECT_STREAMING_MODE);
++ g_string_append_printf (lines, ":");
++ g_string_append_printf (lines, " active");
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ if (msg->tcp_ports) {
++ g_string_append_printf (lines, GST_STRING_WFD2_TRANSPORT_SWITCH);
++ if (msg->tcp_ports->profile) {
++ g_string_append_printf (lines, ":");
++ g_string_append_printf (lines, " %s", msg->tcp_ports->profile);
++ g_string_append_printf (lines, " %d", msg->tcp_ports->rtp_port0);
++ g_string_append_printf (lines, " %d", msg->tcp_ports->rtp_port1);
++ g_string_append_printf (lines, " %s", msg->tcp_ports->mode);
++ }
++ g_string_append_printf (lines, "\r\n");
++ }
++
++ if (msg->buf_len) {
++ g_string_append_printf (lines, GST_STRING_WFD2_BUFFER_LEN);
++ g_string_append_printf (lines, ":");
++ g_string_append_printf (lines, " %d", msg->buf_len->buf_len);
++ 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 wfd2 audio codecs */
++ if (msg->wfd2_audio_codecs) {
++ g_string_append_printf (lines, GST_STRING_WFD2_AUDIO_CODECS);
++ g_string_append_printf (lines, "\r\n");
++ }
++ /* list of direct video codecs */
++ if (msg->direct_video_formats) {
++ g_string_append_printf (lines, GST_STRING_WFD2_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");
++ }
++ if (msg->tcp_ports) {
++ g_string_append_printf (lines, GST_STRING_WFD2_TRANSPORT_SWITCH);
++ g_string_append_printf (lines, "\r\n");
++ }
++ if (msg->buf_len) {
++ g_string_append_printf (lines, GST_STRING_WFD2_BUFFER_LEN);
++ g_string_append_printf (lines, "\r\n");
++ }
++ if (msg->audio_status) {
++ g_string_append_printf (lines, GST_STRING_WFD2_AUDIO_STATUS);
++ g_string_append_printf (lines, "\r\n");
++ }
++ if (msg->video_status) {
++ g_string_append_printf (lines, GST_STRING_WFD2_VIDEO_STATUS);
++ 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 Width(horizontal resolution): %04d\n",
++ msg->video_formats->list->H264_codec.max_hres);
++ }
++ if (msg->video_formats->list->H264_codec.max_vres) {
++ g_print (" Max Height(vertical resolution): %04d\n",
++ msg->video_formats->list->H264_codec.max_vres);
++ }
++ }
++ }
++
++ if (msg->wfd2_audio_codecs) {
++ guint i = 0;
++ g_print ("Audio supported codecs for R2 : \n");
++ for (; i < msg->wfd2_audio_codecs->count; i++) {
++ g_print ("Codec: %s\n", msg->wfd2_audio_codecs->list[i].audio_format);
++ if (!strcmp (msg->wfd2_audio_codecs->list[i].audio_format, "LPCM")) {
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_FREQ_44100)
++ g_print (" Freq: %d\n", 44100);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_FREQ_48000)
++ g_print (" Freq: %d\n", 48000);
++ g_print (" Channels: %d\n", 2);
++ }
++ if (!strcmp (msg->wfd2_audio_codecs->list[i].audio_format, "AAC")) {
++ g_print (" Freq: %d\n", 48000);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_2)
++ g_print (" Channels: %d\n", 2);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_4)
++ g_print (" Channels: %d\n", 4);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_6)
++ g_print (" Channels: %d\n", 6);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_8)
++ g_print (" Channels: %d\n", 8);
++ }
++ if (!strcmp (msg->wfd2_audio_codecs->list[i].audio_format, "AC3")) {
++ g_print (" Freq: %d\n", 48000);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_2)
++ g_print (" Channels: %d\n", 2);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_4)
++ g_print (" Channels: %d\n", 4);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_6)
++ g_print (" Channels: %d\n", 6);
++ }
++ if (!strcmp (msg->wfd2_audio_codecs->list[i].audio_format, "CTA")) {
++ g_print (" Freq: %d\n", 48000);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_2)
++ g_print (" Channels: %d\n", 2);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_4)
++ g_print (" Channels: %d\n", 4);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_6)
++ g_print (" Channels: %d\n", 6);
++ }
++ if (!strcmp (msg->wfd2_audio_codecs->list[i].audio_format, "AAC-ELDv2")) {
++ g_print (" Freq: %d\n", 48000);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_2)
++ g_print (" Channels: %d\n", 2);
++ if (msg->wfd2_audio_codecs->list[i].modes & GST_WFD_CHANNEL_4)
++ g_print (" Channels: %d\n", 4);
++ if (msg->wfd2_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->wfd2_audio_codecs->list[i].latency);
++ }
++ }
++
++
++ if (msg->direct_video_formats) {
++ g_print ("Video supported formats for direct streaming : \n");
++ if (msg->direct_video_formats->list) {
++ guint nativeindex = 0;
++ g_print ("Codec: H264\n");
++ if ((msg->direct_video_formats->list->native & 0x7) ==
++ GST_WFD_VIDEO_CEA_RESOLUTION) {
++ g_print (" Native type: CEA\n");
++ } else if ((msg->direct_video_formats->list->native & 0x7) ==
++ GST_WFD_VIDEO_VESA_RESOLUTION) {
++ g_print (" Native type: VESA\n");
++ } else if ((msg->direct_video_formats->list->native & 0x7) ==
++ GST_WFD_VIDEO_HH_RESOLUTION) {
++ g_print (" Native type: HH\n");
++ }
++ nativeindex = msg->direct_video_formats->list->native >> 3;
++ g_print (" Resolution: %d\n", (1 << nativeindex));
++
++ if (msg->direct_video_formats->list->
++ H264_codec.profile & GST_WFD_H264_BASE_PROFILE) {
++ g_print (" Profile: BASE\n");
++ } else if (msg->direct_video_formats->list->
++ H264_codec.profile & GST_WFD_H264_HIGH_PROFILE) {
++ g_print (" Profile: HIGH\n");
++ }
++ if (msg->direct_video_formats->list->H264_codec.level & GST_WFD_H264_LEVEL_3_1) {
++ g_print (" Level: 3.1\n");
++ } else if (msg->direct_video_formats->list->
++ H264_codec.level & GST_WFD_H264_LEVEL_3_2) {
++ g_print (" Level: 3.2\n");
++ } else if (msg->direct_video_formats->list->
++ H264_codec.level & GST_WFD_H264_LEVEL_4) {
++ g_print (" Level: 4\n");
++ } else if (msg->direct_video_formats->list->
++ H264_codec.level & GST_WFD_H264_LEVEL_4_1) {
++ g_print (" Level: 4.1\n");
++ } else if (msg->direct_video_formats->list->
++ H264_codec.level & GST_WFD_H264_LEVEL_4_2) {
++ g_print (" Level: 4.2\n");
++ }
++ g_print (" Latency: %d\n",
++ msg->direct_video_formats->list->H264_codec.misc_params.latency);
++ g_print (" min_slice_size: %x\n",
++ msg->direct_video_formats->list->H264_codec.misc_params.min_slice_size);
++ g_print (" slice_enc_params: %x\n",
++ msg->direct_video_formats->list->H264_codec.misc_params.slice_enc_params);
++ g_print (" frame_rate_control_support: %x\n",
++ msg->direct_video_formats->list->H264_codec.
++ misc_params.frame_rate_control_support);
++ if (msg->direct_video_formats->list->H264_codec.max_hres) {
++ g_print (" Max Width(horizontal resolution): %04d\n",
++ msg->direct_video_formats->list->H264_codec.max_hres);
++ }
++ if (msg->direct_video_formats->list->H264_codec.max_vres) {
++ g_print (" Max height(vertical resolution): %04d\n",
++ msg->direct_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");
++ }
++
++ if (msg->direct_mode) {
++ g_print (GST_STRING_WFD2_DIRECT_STREAMING_MODE);
++ g_print ("\r\n");
++ }
++
++ if (msg->tcp_ports) {
++ g_print (" TCP Ports : \n");
++ if (msg->tcp_ports->profile) {
++ g_print ("%s\n", msg->tcp_ports->profile);
++ g_print (" %d\n", msg->tcp_ports->rtp_port0);
++ g_print (" %d\n", msg->tcp_ports->rtp_port1);
++ g_print (" %s\n", msg->tcp_ports->mode);
++ }
++ g_print ("\r\n");
++ }
++
++ if (msg->buf_len) {
++ g_print (" Buffer Length : %d\n", msg->buf_len->buf_len);
++ g_print ("\r\n");
++ }
++
++ if (msg->audio_status) {
++ g_print ("Audio Playback Status : \n");
++ g_print (" Current audio buffer size : %d\n", msg->audio_status->aud_bufsize);
++ g_print (" Current audio decoded PTS : %lld\n", msg->audio_status->aud_pts);
++ g_print ("\r\n");
++ }
++
++ if (msg->video_status) {
++ g_print ("Video Playback Status : \n");
++ g_print (" Current video buffer size : %d\n", msg->video_status->vid_bufsize);
++ g_print (" Current video decoded PTS : %lld\n", msg->video_status->vid_pts);
++ 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 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) {
++
++ if (a_codec & GST_WFD_AUDIO_LPCM)
++ msg->audio_codecs->count++;
++ 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++) {
++ 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_preferred_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);
++
++ if (a_codec == GST_WFD_AUDIO_UNKNOWN) {
++ msg->audio_codecs->list = NULL;
++ msg->audio_codecs->count = 0;
++ return GST_WFD_OK;
++ }
++ 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_preferred_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);
++
++ temp >>= 1;
++ while (temp) {
++ nativeindex++;
++ temp >>= 1;
++ }
++
++ msg->video_formats->list->native = nativeindex;
++ 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_width;
++ msg->video_formats->list->H264_codec.max_vres = v_max_height;
++ 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_preferred_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);
++
++ if (v_codec == GST_WFD_VIDEO_UNKNOWN) {
++ msg->video_formats->list = NULL;
++ msg->video_formats->count = 0;
++ return GST_WFD_OK;
++ }
++
++ 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_width;
++ msg->video_formats->list->H264_codec.max_vres = v_max_height;
++ 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_width = msg->video_formats->list->H264_codec.max_hres;
++ *v_max_height = 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_preferred_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_width = msg->video_formats->list->H264_codec.max_hres;
++ *v_max_height = 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_supported_wfd2_audio_codec (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, cta = 0, aac_eldv2 = 0;
++
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (!msg->wfd2_audio_codecs)
++ msg->wfd2_audio_codecs = g_new0 (GstWFD2AudioCodeclist, 1);
++
++ if (a_codec != GST_WFD_AUDIO_UNKNOWN) {
++ while (temp) {
++ msg->wfd2_audio_codecs->count++;
++ temp >>= 1;
++ }
++ msg->wfd2_audio_codecs->list =
++ g_new0 (GstWFDAudioCodec, msg->wfd2_audio_codecs->count);
++ for (; i < msg->wfd2_audio_codecs->count; i++) {
++ if ((a_codec & GST_WFD_AUDIO_LPCM) && (!pcm)) {
++ msg->wfd2_audio_codecs->list[i].audio_format = g_strdup ("LPCM");
++ msg->wfd2_audio_codecs->list[i].modes = a_freq;
++ msg->wfd2_audio_codecs->list[i].latency = a_latency;
++ pcm = 1;
++ } else if ((a_codec & GST_WFD_AUDIO_AAC) && (!aac)) {
++ msg->wfd2_audio_codecs->list[i].audio_format = g_strdup ("AAC");
++ msg->wfd2_audio_codecs->list[i].modes = a_channels;
++ msg->wfd2_audio_codecs->list[i].latency = a_latency;
++ aac = 1;
++ } else if ((a_codec & GST_WFD_AUDIO_AC3) && (!ac3)) {
++ msg->wfd2_audio_codecs->list[i].audio_format = g_strdup ("AC3");
++ msg->wfd2_audio_codecs->list[i].modes = a_channels;
++ msg->wfd2_audio_codecs->list[i].latency = a_latency;
++ ac3 = 1;
++ } else if ((a_codec & GST_WFD_AUDIO_CTA) && (!cta)) {
++ msg->wfd2_audio_codecs->list[i].audio_format = g_strdup ("CTA");
++ msg->wfd2_audio_codecs->list[i].modes = a_channels;
++ msg->wfd2_audio_codecs->list[i].latency = a_latency;
++ cta = 1;
++ } else if ((a_codec & GST_WFD_AUDIO_AAC_ELDV2) && (!aac_eldv2)) {
++ msg->wfd2_audio_codecs->list[i].audio_format = g_strdup ("AAC-ELDv2");
++ msg->wfd2_audio_codecs->list[i].modes = a_channels;
++ msg->wfd2_audio_codecs->list[i].latency = a_latency;
++ aac_eldv2 = 1;
++ }
++ }
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_set_preferred_wfd2_audio_codec (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->wfd2_audio_codecs)
++ msg->wfd2_audio_codecs = g_new0 (GstWFD2AudioCodeclist, 1);
++
++ msg->wfd2_audio_codecs->list = g_new0 (GstWFDAudioCodec, 1);
++ msg->wfd2_audio_codecs->count = 1;
++ if (a_codec == GST_WFD_AUDIO_LPCM) {
++ msg->wfd2_audio_codecs->list->audio_format = g_strdup ("LPCM");
++ msg->wfd2_audio_codecs->list->modes = a_freq;
++ msg->wfd2_audio_codecs->list->latency = a_latency;
++ } else if (a_codec == GST_WFD_AUDIO_AAC) {
++ msg->wfd2_audio_codecs->list->audio_format = g_strdup ("AAC");
++ msg->wfd2_audio_codecs->list->modes = a_channels;
++ msg->wfd2_audio_codecs->list->latency = a_latency;
++ } else if (a_codec == GST_WFD_AUDIO_AC3) {
++ msg->wfd2_audio_codecs->list->audio_format = g_strdup ("AC3");
++ msg->wfd2_audio_codecs->list->modes = a_channels;
++ msg->wfd2_audio_codecs->list->latency = a_latency;
++ } else if (a_codec == GST_WFD_AUDIO_CTA) {
++ msg->wfd2_audio_codecs->list->audio_format = g_strdup ("CTA");
++ msg->wfd2_audio_codecs->list->modes = a_channels;
++ msg->wfd2_audio_codecs->list->latency = a_latency;
++ } else if (a_codec == GST_WFD_AUDIO_AAC_ELDV2) {
++ msg->wfd2_audio_codecs->list->audio_format = g_strdup ("AAC-ELDv2");
++ msg->wfd2_audio_codecs->list->modes = a_channels;
++ msg->wfd2_audio_codecs->list->latency = a_latency;
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_supported_wfd2_audio_codec (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->wfd2_audio_codecs != NULL, GST_WFD_EINVAL);
++
++ for (; i < msg->wfd2_audio_codecs->count; i++) {
++ if (!g_strcmp0 (msg->wfd2_audio_codecs->list[i].audio_format, "LPCM")) {
++ *a_codec |= GST_WFD_AUDIO_LPCM;
++ *a_freq |= msg->wfd2_audio_codecs->list[i].modes;
++ *a_channels |= GST_WFD_CHANNEL_2;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list[i].latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list[i].audio_format, "AAC")) {
++ *a_codec |= GST_WFD_AUDIO_AAC;
++ *a_freq |= GST_WFD_FREQ_48000;
++ *a_channels |= msg->wfd2_audio_codecs->list[i].modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list[i].latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list[i].audio_format, "AC3")) {
++ *a_codec |= GST_WFD_AUDIO_AC3;
++ *a_freq |= GST_WFD_FREQ_48000;
++ *a_channels |= msg->wfd2_audio_codecs->list[i].modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list[i].latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list[i].audio_format, "CTA")) {
++ *a_codec |= GST_WFD_AUDIO_CTA;
++ *a_freq |= GST_WFD_FREQ_48000;
++ *a_channels |= msg->wfd2_audio_codecs->list[i].modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list[i].latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list[i].audio_format, "AAC-ELDv2")) {
++ *a_codec |= GST_WFD_AUDIO_AAC_ELDV2;
++ *a_freq |= GST_WFD_FREQ_48000;
++ *a_channels |= msg->wfd2_audio_codecs->list[i].modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list[i].latency;
++ }
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_preferred_wfd2_audio_codec (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->wfd2_audio_codecs->list->audio_format, "LPCM")) {
++ *a_codec = GST_WFD_AUDIO_LPCM;
++ *a_freq = msg->wfd2_audio_codecs->list->modes;
++ *a_channels = GST_WFD_CHANNEL_2;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list->latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list->audio_format, "AAC")) {
++ *a_codec = GST_WFD_AUDIO_AAC;
++ *a_freq = GST_WFD_FREQ_48000;
++ *a_channels = msg->wfd2_audio_codecs->list->modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list->latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list->audio_format, "AC3")) {
++ *a_codec = GST_WFD_AUDIO_AC3;
++ *a_freq = GST_WFD_FREQ_48000;
++ *a_channels = msg->wfd2_audio_codecs->list->modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list->latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list->audio_format, "CTA")) {
++ *a_codec = GST_WFD_AUDIO_CTA;
++ *a_freq = GST_WFD_FREQ_48000;
++ *a_channels = msg->wfd2_audio_codecs->list->modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list->latency;
++ } else if (!g_strcmp0 (msg->wfd2_audio_codecs->list->audio_format, "AAC-ELDv2")) {
++ *a_codec = GST_WFD_AUDIO_AAC_ELDV2;
++ *a_freq = GST_WFD_FREQ_48000;
++ *a_channels = msg->wfd2_audio_codecs->list->modes;
++ *a_bitwidth = 16;
++ *a_latency = msg->wfd2_audio_codecs->list->latency;
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_set_supported_direct_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->direct_video_formats)
++ msg->direct_video_formats = g_new0 (GstWFD2VideoCodeclist, 1);
++
++ if (v_codec != GST_WFD_VIDEO_UNKNOWN) {
++ msg->direct_video_formats->list = g_new0 (GstWFDVideoCodec, 1);
++ while (temp) {
++ nativeindex++;
++ temp >>= 1;
++ }
++
++ if (nativeindex) msg->direct_video_formats->list->native = nativeindex - 1;
++ msg->direct_video_formats->list->native <<= 3;
++
++ if (v_native == GST_WFD_VIDEO_VESA_RESOLUTION)
++ msg->direct_video_formats->list->native |= 1;
++ else if (v_native == GST_WFD_VIDEO_HH_RESOLUTION)
++ msg->direct_video_formats->list->native |= 2;
++
++ msg->direct_video_formats->list->preferred_display_mode_supported = 1;
++ msg->direct_video_formats->list->H264_codec.profile = v_profile;
++ msg->direct_video_formats->list->H264_codec.level = v_level;
++ msg->direct_video_formats->list->H264_codec.max_hres = v_max_width;
++ msg->direct_video_formats->list->H264_codec.max_vres = v_max_height;
++ msg->direct_video_formats->list->H264_codec.misc_params.CEA_Support =
++ v_cea_resolution;
++ msg->direct_video_formats->list->H264_codec.misc_params.VESA_Support =
++ v_vesa_resolution;
++ msg->direct_video_formats->list->H264_codec.misc_params.HH_Support =
++ v_hh_resolution;
++ msg->direct_video_formats->list->H264_codec.misc_params.latency = v_latency;
++ msg->direct_video_formats->list->H264_codec.misc_params.min_slice_size =
++ min_slice_size;
++ msg->direct_video_formats->list->H264_codec.misc_params.slice_enc_params =
++ slice_enc_params;
++ msg->direct_video_formats->list->H264_codec.
++ misc_params.frame_rate_control_support = frame_rate_control;
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_set_preferred_direct_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,
++ 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->direct_video_formats)
++ msg->direct_video_formats = g_new0 (GstWFD2VideoCodeclist, 1);
++ msg->direct_video_formats->list = g_new0 (GstWFDVideoCodec, 1);
++
++ while (temp) {
++ nativeindex++;
++ temp >>= 1;
++ }
++
++ if (nativeindex)
++ msg->direct_video_formats->list->native = nativeindex - 1;
++ msg->direct_video_formats->list->native <<= 3;
++
++ if (v_native == GST_WFD_VIDEO_VESA_RESOLUTION)
++ msg->direct_video_formats->list->native |= 1;
++ else if (v_native == GST_WFD_VIDEO_HH_RESOLUTION)
++ msg->direct_video_formats->list->native |= 2;
++
++ msg->direct_video_formats->list->preferred_display_mode_supported = 0;
++ msg->direct_video_formats->list->H264_codec.profile = v_profile;
++ msg->direct_video_formats->list->H264_codec.level = v_level;
++ msg->direct_video_formats->list->H264_codec.max_hres = v_max_width;
++ msg->direct_video_formats->list->H264_codec.max_vres = v_max_height;
++ msg->direct_video_formats->list->H264_codec.misc_params.CEA_Support =
++ v_cea_resolution;
++ msg->direct_video_formats->list->H264_codec.misc_params.VESA_Support =
++ v_vesa_resolution;
++ msg->direct_video_formats->list->H264_codec.misc_params.HH_Support = v_hh_resolution;
++ msg->direct_video_formats->list->H264_codec.misc_params.latency = v_latency;
++ msg->direct_video_formats->list->H264_codec.misc_params.min_slice_size =
++ min_slice_size;
++ msg->direct_video_formats->list->H264_codec.misc_params.slice_enc_params =
++ slice_enc_params;
++ msg->direct_video_formats->list->H264_codec.misc_params.frame_rate_control_support =
++ frame_rate_control;
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_supported_direct_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->direct_video_formats->list->native & 0x7;
++ nativeindex = msg->direct_video_formats->list->native >> 3;
++ *v_native_resolution = ((guint64) 1) << nativeindex;
++ *v_profile = msg->direct_video_formats->list->H264_codec.profile;
++ *v_level = msg->direct_video_formats->list->H264_codec.level;
++ *v_max_width = msg->direct_video_formats->list->H264_codec.max_hres;
++ *v_max_height = msg->direct_video_formats->list->H264_codec.max_vres;
++ *v_cea_resolution =
++ msg->direct_video_formats->list->H264_codec.misc_params.CEA_Support;
++ *v_vesa_resolution =
++ msg->direct_video_formats->list->H264_codec.misc_params.VESA_Support;
++ *v_hh_resolution =
++ msg->direct_video_formats->list->H264_codec.misc_params.HH_Support;
++ *v_latency = msg->direct_video_formats->list->H264_codec.misc_params.latency;
++ *min_slice_size =
++ msg->direct_video_formats->list->H264_codec.misc_params.min_slice_size;
++ *slice_enc_params =
++ msg->direct_video_formats->list->H264_codec.misc_params.slice_enc_params;
++ *frame_rate_control =
++ msg->direct_video_formats->list->H264_codec.
++ misc_params.frame_rate_control_support;
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_preferred_direct_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,
++ 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->direct_video_formats->list->native & 0x7;
++ nativeindex = msg->direct_video_formats->list->native >> 3;
++ *v_native_resolution = ((guint64) 1) << nativeindex;
++ *v_profile = msg->direct_video_formats->list->H264_codec.profile;
++ *v_level = msg->direct_video_formats->list->H264_codec.level;
++ *v_max_width = msg->direct_video_formats->list->H264_codec.max_hres;
++ *v_max_height = msg->direct_video_formats->list->H264_codec.max_vres;
++ *v_cea_resolution =
++ msg->direct_video_formats->list->H264_codec.misc_params.CEA_Support;
++ *v_vesa_resolution =
++ msg->direct_video_formats->list->H264_codec.misc_params.VESA_Support;
++ *v_hh_resolution =
++ msg->direct_video_formats->list->H264_codec.misc_params.HH_Support;
++ *v_latency = msg->direct_video_formats->list->H264_codec.misc_params.latency;
++ *min_slice_size =
++ msg->direct_video_formats->list->H264_codec.misc_params.min_slice_size;
++ *slice_enc_params =
++ msg->direct_video_formats->list->H264_codec.misc_params.slice_enc_params;
++ *frame_rate_control =
++ msg->direct_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_coupled_sink(GstWFDMessage *msg,
++ GstWFDCoupledSinkStatus status, gchar *sink_address, gboolean sink_supported)
++{
++ g_return_val_if_fail(msg != NULL, GST_WFD_EINVAL);
++ if (!msg->coupled_sink) msg->coupled_sink = g_new0(GstWFDCoupledSink, 1);
++ if (!sink_supported) return GST_WFD_OK;
++ msg->coupled_sink->coupled_sink_cap = g_new0(GstWFDCoupledSinkCap, 1);
++ msg->coupled_sink->coupled_sink_cap->status = status;
++ msg->coupled_sink->coupled_sink_cap->sink_address = g_strdup(sink_address);
++ msg->coupled_sink->coupled_sink_cap->sink_supported = sink_supported;
++ return GST_WFD_OK;
++}
++
++GstWFDResult gst_wfd_message_get_coupled_sink(GstWFDMessage *msg,
++ GstWFDCoupledSinkStatus *status, gchar **sink_address, gboolean *sink_supported)
++{
++ g_return_val_if_fail(msg != NULL, GST_WFD_EINVAL);
++ if (msg->coupled_sink
++ && msg->coupled_sink->coupled_sink_cap->sink_supported) {
++ *status = msg->coupled_sink->coupled_sink_cap->status;
++ *sink_address = g_strdup(msg->coupled_sink->coupled_sink_cap->sink_address);
++ *sink_supported = (gboolean)msg->coupled_sink->coupled_sink_cap->sink_supported;
++ }
++ else {
++ *status = GST_WFD_SINK_NOT_COUPLED;
++ }
++ 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);
++ *hdcpversion = GST_WFD_HDCP_NONE;
++ *TCPPort = 0;
++
++ 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"))
++ return GST_WFD_OK;
++ else 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
++ return GST_WFD_OK;
++
++ if (!msg->content_protection->hdcp2_spec->TCPPort)
++ return GST_WFD_OK;
++
++ result = strtok_r (msg->content_protection->hdcp2_spec->TCPPort, "=", &ptr);
++ if (result == NULL || ptr == NULL)
++ return GST_WFD_OK;
++
++ result = strtok_r (NULL, "=", &ptr);
++ if (result)
++ *TCPPort = atoi(result);
++ }
++ return GST_WFD_OK;
++}
++
++
++GstWFDResult
++gst_wfd_messge_set_preferred_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_preferred_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;
++}
++
++GstWFDResult
++gst_wfd_message_set_direct_streaming_mode(GstWFDMessage *msg, gboolean enable)
++{
++ g_return_val_if_fail(msg != NULL, GST_WFD_EINVAL);
++
++ if (!msg->direct_mode)
++ msg->direct_mode = g_new0(GstWFD2DirectStreamingMode, 1);
++
++ msg->direct_mode->direct_mode = enable;
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_messge_set_preferred_tcp_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->tcp_ports)
++ msg->tcp_ports = g_new0 (GstWFDTCPPorts, 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->tcp_ports->profile = g_string_free (lines, FALSE);
++ msg->tcp_ports->rtp_port0 = rtp_port0;
++ msg->tcp_ports->rtp_port1 = rtp_port1;
++ msg->tcp_ports->mode = g_strdup ("mode=play");
++ }
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_preferred_tcp_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->tcp_ports != NULL, GST_WFD_EINVAL);
++
++ if (g_strrstr (msg->tcp_ports->profile, "RTP"))
++ *trans = GST_WFD_RTSP_TRANS_RTP;
++ if (g_strrstr (msg->tcp_ports->profile, "RDT"))
++ *trans = GST_WFD_RTSP_TRANS_RDT;
++ if (g_strrstr (msg->tcp_ports->profile, "AVP"))
++ *profile = GST_WFD_RTSP_PROFILE_AVP;
++ if (g_strrstr (msg->tcp_ports->profile, "SAVP"))
++ *profile = GST_WFD_RTSP_PROFILE_SAVP;
++ if (g_strrstr (msg->tcp_ports->profile, "UDP;unicast"))
++ *lowertrans = GST_WFD_RTSP_LOWER_TRANS_UDP;
++ if (g_strrstr (msg->tcp_ports->profile, "UDP;multicast"))
++ *lowertrans = GST_WFD_RTSP_LOWER_TRANS_UDP_MCAST;
++ if (g_strrstr (msg->tcp_ports->profile, "TCP;unicast"))
++ *lowertrans = GST_WFD_RTSP_LOWER_TRANS_TCP;
++ if (g_strrstr (msg->tcp_ports->profile, "HTTP"))
++ *lowertrans = GST_WFD_RTSP_LOWER_TRANS_HTTP;
++
++ *rtp_port0 = msg->tcp_ports->rtp_port0;
++ *rtp_port1 = msg->tcp_ports->rtp_port1;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_set_buffer_length (GstWFDMessage *msg, guint buf_len)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++
++ if (!msg->buf_len)
++ msg->buf_len = g_new0 (GstWFDBufferLen, 1);
++ msg->buf_len->buf_len = buf_len;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_buffer_length (GstWFDMessage *msg, guint *buf_len)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (msg->buf_len != NULL, GST_WFD_EINVAL);
++
++ *buf_len = msg->buf_len->buf_len;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_audio_playback_status (GstWFDMessage *msg,
++ guint *bufsize,
++ guint64 *pts)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (msg->audio_status != NULL, GST_WFD_EINVAL);
++
++ *bufsize = msg->audio_status->aud_bufsize;
++ *pts = msg->audio_status->aud_pts;
++
++ return GST_WFD_OK;
++}
++
++GstWFDResult
++gst_wfd_message_get_video_playback_status (GstWFDMessage *msg,
++ guint *bufsize,
++ guint64 *pts)
++{
++ g_return_val_if_fail (msg != NULL, GST_WFD_EINVAL);
++ g_return_val_if_fail (msg->video_status != NULL, GST_WFD_EINVAL);
++
++ *bufsize = msg->video_status->vid_bufsize;
++ *pts = msg->video_status->vid_pts;
++
++ return GST_WFD_OK;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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 <glib.h>
++#include <gst/gst.h>
++
++#include "rtsp-server-prelude.h"
++
++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"
++
++#define GST_STRING_WFD2_AUDIO_CODECS "wfd2_audio_codecs"
++#define GST_STRING_WFD2_VIDEO_FORMATS "wfd2_video_formats"
++#define GST_STRING_WFD2_DIRECT_STREAMING_MODE "wfd2_direct_streaming_mode"
++
++#define GST_STRING_WFD2_TRANSPORT_SWITCH "wfd2_transport_switch"
++#define GST_STRING_WFD2_BUFFER_LEN "wfd2_buffer_len"
++#define GST_STRING_WFD2_AUDIO_STATUS "wfd2_audio_playback_status"
++#define GST_STRING_WFD2_VIDEO_STATUS "wfd2_video_playback_status"
++
++/**
++ * 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),
++ GST_WFD_AUDIO_CTA = (1 << 3),
++ GST_WFD_AUDIO_AAC_ELDV2 = (1 << 4)
++} 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_NOT_COUPLED = 0,
++ GST_WFD_SINK_COUPLED = (1 << 0),
++ GST_WFD_SINK_TEARDOWN_COUPLING = (1 << 1)
++} 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 count;
++ GstWFDAudioCodec *list;
++} GstWFD2AudioCodeclist;
++
++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 count;
++ GstWFDVideoCodec *list;
++} GstWFD2VideoCodeclist;
++
++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;
++ gboolean sink_supported;
++} GstWFDCoupledSinkCap;
++
++typedef struct {
++ GstWFDCoupledSinkCap *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;
++
++typedef struct {
++ gboolean direct_mode;
++} GstWFD2DirectStreamingMode;
++
++typedef struct {
++ gchar *profile;
++ guint32 rtp_port0;
++ guint32 rtp_port1;
++ gchar *mode;
++} GstWFDTCPPorts;
++
++typedef struct {
++ guint buf_len;
++} GstWFDBufferLen;
++
++typedef struct {
++ guint aud_bufsize;
++ guint64 aud_pts;
++} GstWFDAudioReport;
++
++typedef struct {
++ guint vid_bufsize;
++ guint64 vid_pts;
++} GstWFDVideoReport;
++
++/**
++ * 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;
++ GstWFD2AudioCodeclist *wfd2_audio_codecs;
++ GstWFD2VideoCodeclist *direct_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;
++ GstWFD2DirectStreamingMode *direct_mode;
++ GstWFDTCPPorts *tcp_ports;
++ GstWFDBufferLen *buf_len;
++ GstWFDAudioReport *audio_status;
++ GstWFDVideoReport *video_status;
++} GstWFDMessage;
++
++GST_RTSP_SERVER_API
++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 */
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_new (GstWFDMessage **msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_init (GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_uninit (GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_free (GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_copy (const GstWFDMessage *msg, GstWFDMessage **copy);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_parse_buffer (const guint8 *data, guint size, GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++gchar* gst_wfd_message_as_text (const GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++gchar* gst_wfd_message_param_names_as_text (const GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_dump (const GstWFDMessage *msg);
++
++GST_RTSP_SERVER_API
++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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_preferred_audio_format(GstWFDMessage *msg,
++ GstWFDAudioFormats a_codec,
++ GstWFDAudioFreq a_freq,
++ GstWFDAudioChannels a_channels,
++ guint a_bitwidth, guint32 a_latency);
++
++GST_RTSP_SERVER_API
++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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_preferred_audio_format (GstWFDMessage *msg,
++ GstWFDAudioFormats *a_codec,
++ GstWFDAudioFreq *a_freq,
++ GstWFDAudioChannels *a_channels,
++ guint *a_bitwidth, guint32 *a_latency);
++
++GST_RTSP_SERVER_API
++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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_preferred_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);
++
++GST_RTSP_SERVER_API
++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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_preferred_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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_supported_wfd2_audio_codec(GstWFDMessage *msg,
++ GstWFDAudioFormats a_codec,
++ guint a_freq, guint a_channels,
++ guint a_bitwidth, guint32 a_latency);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_preferred_wfd2_audio_codec(GstWFDMessage *msg,
++ GstWFDAudioFormats a_codec,
++ GstWFDAudioFreq a_freq,
++ GstWFDAudioChannels a_channels,
++ guint a_bitwidth, guint32 a_latency);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_supported_wfd2_audio_codec (GstWFDMessage *msg,
++ guint *a_codec,
++ guint *a_freq,
++ guint *a_channels,
++ guint *a_bitwidth,
++ guint32 *a_latency);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_preferred_wfd2_audio_codec (GstWFDMessage *msg,
++ GstWFDAudioFormats *a_codec,
++ GstWFDAudioFreq *a_freq,
++ GstWFDAudioChannels *a_channels,
++ guint *a_bitwidth, guint32 *a_latency);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_supported_direct_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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_preferred_direct_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,
++ 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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_supported_direct_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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_preferred_direct_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,
++ 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);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_display_edid (GstWFDMessage *msg,
++ gboolean edid_supported,
++ guint32 edid_blockcount,
++ gchar *edid_playload);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_display_edid (GstWFDMessage *msg,
++ gboolean *edid_supported,
++ guint32 *edid_blockcount,
++ gchar **edid_playload);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_coupled_sink(GstWFDMessage *msg,
++ GstWFDCoupledSinkStatus status,
++ gchar *sink_address,
++ gboolean sink_supported);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_coupled_sink(GstWFDMessage *msg,
++ GstWFDCoupledSinkStatus *status,
++ gchar **sink_address,
++ gboolean *sink_supported);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_contentprotection_type (GstWFDMessage *msg,
++ GstWFDHDCPProtection hdcpversion,
++ guint32 TCPPort);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_contentprotection_type (GstWFDMessage *msg,
++ GstWFDHDCPProtection *hdcpversion,
++ guint32 *TCPPort);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_messge_set_preferred_rtp_ports (GstWFDMessage *msg,
++ GstWFDRTSPTransMode trans,
++ GstWFDRTSPProfile profile,
++ GstWFDRTSPLowerTrans lowertrans,
++ guint32 rtp_port0,
++ guint32 rtp_port1);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_preferred_rtp_ports (GstWFDMessage *msg,
++ GstWFDRTSPTransMode *trans,
++ GstWFDRTSPProfile *profile,
++ GstWFDRTSPLowerTrans *lowertrans,
++ guint32 *rtp_port0,
++ guint32 *rtp_port1);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_presentation_url(GstWFDMessage *msg,
++ gchar *wfd_url0, gchar *wfd_url1);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_presentation_url(GstWFDMessage *msg, gchar **wfd_url0,
++ gchar **wfd_url1);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_av_format_change_timing(GstWFDMessage *msg,
++ guint64 PTS,
++ guint64 DTS);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_av_format_change_timing(GstWFDMessage *msg,
++ guint64 *PTS,
++ guint64 *DTS);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_direct_streaming_mode(GstWFDMessage *msg,
++ gboolean enable);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_messge_set_preferred_tcp_ports (GstWFDMessage *msg,
++ GstWFDRTSPTransMode trans,
++ GstWFDRTSPProfile profile,
++ GstWFDRTSPLowerTrans lowertrans,
++ guint32 rtp_port0,
++ guint32 rtp_port1);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_preferred_tcp_ports (GstWFDMessage *msg,
++ GstWFDRTSPTransMode *trans,
++ GstWFDRTSPProfile *profile,
++ GstWFDRTSPLowerTrans *lowertrans,
++ guint32 *rtp_port0,
++ guint32 *rtp_port1);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_set_buffer_length (GstWFDMessage *msg,
++ guint buf_len);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_buffer_length (GstWFDMessage *msg,
++ guint *buf_len);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_audio_playback_status (GstWFDMessage *msg,
++ guint *bufsize,
++ guint64 *pts);
++
++GST_RTSP_SERVER_API
++GstWFDResult gst_wfd_message_get_video_playback_status (GstWFDMessage *msg,
++ guint *bufsize,
++ guint64 *pts);
++G_END_DECLS
++
++#endif /* __GST_WFD_MESSAGE_H__ */
--- /dev/null
+ rtsp_server_sources = [
+ 'rtsp-address-pool.c',
+ 'rtsp-auth.c',
+ 'rtsp-client.c',
+ 'rtsp-context.c',
+ 'rtsp-latency-bin.c',
+ 'rtsp-media.c',
+ 'rtsp-media-factory.c',
+ 'rtsp-media-factory-uri.c',
+ 'rtsp-mount-points.c',
+ 'rtsp-params.c',
+ 'rtsp-permissions.c',
+ 'rtsp-sdp.c',
+ 'rtsp-server.c',
+ 'rtsp-session.c',
+ 'rtsp-session-media.c',
+ 'rtsp-session-pool.c',
+ 'rtsp-stream.c',
+ 'rtsp-stream-transport.c',
+ 'rtsp-thread-pool.c',
+ 'rtsp-token.c',
+ 'rtsp-onvif-server.c',
+ 'rtsp-onvif-client.c',
+ 'rtsp-onvif-media-factory.c',
+ 'rtsp-onvif-media.c',
++ 'rtsp-media-ext.c',
++ 'rtsp-media-factory-wfd.c',
++ 'gstwfdmessage-ext.c',
++ 'gstwfdmessage.c',
++ 'rtsp-client-ext.c',
++ 'rtsp-client-wfd.c',
++ 'rtsp-server-wfd.c',
+ ]
+
+ rtsp_server_headers = [
+ 'rtsp-auth.h',
+ 'rtsp-address-pool.h',
+ 'rtsp-context.h',
+ 'rtsp-params.h',
+ 'rtsp-sdp.h',
+ 'rtsp-thread-pool.h',
+ 'rtsp-media.h',
+ 'rtsp-media-factory.h',
+ 'rtsp-media-factory-uri.h',
+ 'rtsp-mount-points.h',
+ 'rtsp-permissions.h',
+ 'rtsp-stream.h',
+ 'rtsp-stream-transport.h',
+ 'rtsp-session.h',
+ 'rtsp-session-media.h',
+ 'rtsp-session-pool.h',
+ 'rtsp-token.h',
+ 'rtsp-client.h',
+ 'rtsp-server.h',
+ 'rtsp-server-object.h',
+ 'rtsp-server-prelude.h',
+ 'rtsp-onvif-server.h',
+ 'rtsp-onvif-client.h',
+ 'rtsp-onvif-media-factory.h',
+ 'rtsp-onvif-media.h',
++ 'rtsp-media-ext.h',
++ 'rtsp-media-factory-wfd.h',
++ 'gstwfdmessage-ext.h',
++ 'gstwfdmessage.h',
++ 'rtsp-client-ext.h',
++ 'rtsp-client-wfd.h',
++ 'rtsp-server-wfd.h',
+ ]
+
+ install_headers(rtsp_server_headers, subdir : 'gstreamer-1.0/gst/rtsp-server')
+
+ gst_rtsp_server_deps = [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep]
+ gst_rtsp_server = library('gstrtspserver-@0@'.format(api_version),
+ rtsp_server_sources,
+ include_directories : rtspserver_incs,
+ c_args: rtspserver_args + ['-DBUILDING_GST_RTSP_SERVER'],
+ version : libversion,
+ soversion : soversion,
+ darwin_versions : osxversion,
+ install : true,
+ dependencies : gst_rtsp_server_deps)
+
+ pkgconfig.generate(gst_rtsp_server,
+ libraries : [gst_dep],
+ subdirs : pkgconfig_subdirs,
+ name : 'gstreamer-rtsp-server-1.0',
+ description : 'GStreamer based RTSP server',
+ )
+
+ rtsp_server_gen_sources = []
+ if build_gir
+ gst_gir_extra_args = gir_init_section + ['--c-include=gst/rtsp-server/rtsp-server.h']
+ rtsp_server_gir = gnome.generate_gir(gst_rtsp_server,
+ sources : rtsp_server_headers + rtsp_server_sources,
+ namespace : 'GstRtspServer',
+ nsversion : api_version,
+ identifier_prefix : 'Gst',
+ symbol_prefix : 'gst',
+ export_packages : 'gstreamer-rtsp-server-' + api_version,
+ install : true,
+ extra_args : gst_gir_extra_args,
+ includes : ['Gst-1.0', 'GstRtsp-1.0', 'GstNet-1.0'],
+ dependencies : gst_rtsp_server_deps,
+ )
+ rtsp_server_gen_sources += [rtsp_server_gir]
+ endif
+
+ gst_rtsp_server_dep = declare_dependency(link_with : gst_rtsp_server,
+ include_directories : rtspserver_incs,
+ sources : rtsp_server_gen_sources,
+ dependencies : [gstrtsp_dep, gstrtp_dep, gstsdp_dep, gstnet_dep, gstapp_dep])
+
+ meson.override_dependency('gstreamer-rtsp-server-1.0', gst_rtsp_server_dep)
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
++ *
++ * 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)
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <stdio.h>
++#include <string.h>
++
++#include "rtsp-client-ext.h"
++#include "rtsp-media-factory-wfd.h"
++#include "rtsp-sdp.h"
++#include "rtsp-params.h"
++#include "rtsp-media-ext.h"
++#include "gstwfdmessage-ext.h"
++
++#define GST_RTSP_EXT_CLIENT_GET_PRIVATE(obj) \
++ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_EXT_CLIENT, GstRTSPExtClientPrivate))
++
++struct _GstRTSPExtClientPrivate
++{
++ GstRTSPMediaExt *media;
++ guint resend_packets;
++ guint prev_max_seqnum;
++ guint prev_fraction_lost;
++ guint32 prev_max_packets_lost;
++ gboolean first_rtcp;
++ guint consecutive_low_bitrate_count;
++
++ guint tizen_retransmission_rtp_port;
++ guint tizen_retransmission_rtcp_port;
++ guint tizen_fec_t_max;
++ guint tizen_fec_p_max;
++ guint tizen_latency_mode;
++};
++
++#define WFD_MOUNT_POINT "/wfd1.0/streamid=0"
++#define UNSTABLE_NETWORK_INTERVAL 15
++#define MIN_PORT_NUM 1024
++#define MAX_PORT_NUM 65535
++#define TIZEN_RETRANSMISSION_RTP_PORT_NONE 0
++#define TIZEN_RETRANSMISSION_RTCP_PORT_NONE 0
++#define MIN_FEC_T_NUM 2
++#define MAX_FEC_T_NUM 100
++#define MIN_FEC_P_NUM 2
++#define MAX_FEC_P_NUM 100
++#define TIZEN_T_MAX_NONE 0
++#define TIZEN_P_MAX_NONE 0
++#define TIZEN_USER_AGENT "TIZEN"
++#define DEFAULT_WFD_TIMEOUT 60
++
++GST_DEBUG_CATEGORY_STATIC (rtsp_ext_client_debug);
++#define GST_CAT_DEFAULT rtsp_ext_client_debug
++
++static gboolean ext_configure_client_media (GstRTSPClient * client,
++ GstRTSPMedia * media, GstRTSPStream * stream, GstRTSPContext * ctx);
++static void handle_ext_stats (GstRTSPWFDClient * client, GstStructure * stats);
++static gchar* handle_ext_m3_req_msg (GstRTSPWFDClient * client, gchar * data);
++static void handle_ext_set_param_msg (GstRTSPWFDClient * client, gchar * data);
++
++static void handle_ext_m3_res_msg (GstRTSPWFDClient * client, gchar * data);
++static gchar* handle_ext_m4_req_msg (GstRTSPWFDClient * client, gchar * data);
++
++static void gst_rtsp_ext_client_finalize (GObject * obj);
++
++G_DEFINE_TYPE (GstRTSPExtClient, gst_rtsp_ext_client, GST_TYPE_RTSP_WFD_CLIENT);
++
++static void
++gst_rtsp_ext_client_class_init (GstRTSPExtClientClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstRTSPClientClass *rtsp_client_class;
++ GstRTSPWFDClientClass *wfd_client_class;
++
++ g_type_class_add_private (klass, sizeof (GstRTSPExtClientPrivate));
++
++ gobject_class = G_OBJECT_CLASS (klass);
++ rtsp_client_class = GST_RTSP_CLIENT_CLASS (klass);
++ wfd_client_class = GST_RTSP_WFD_CLIENT_CLASS (klass);
++
++ gobject_class->finalize = gst_rtsp_ext_client_finalize;
++
++ rtsp_client_class->configure_client_media = ext_configure_client_media;
++ wfd_client_class->wfd_rtp_stats = handle_ext_stats;
++ wfd_client_class->wfd_handle_m3_req_msg = handle_ext_m3_req_msg;
++ wfd_client_class->wfd_handle_m3_res_msg = handle_ext_m3_res_msg;
++ wfd_client_class->wfd_handle_m4_req_msg = handle_ext_m4_req_msg;
++ wfd_client_class->wfd_handle_set_param_msg = handle_ext_set_param_msg;
++
++ GST_DEBUG_CATEGORY_INIT (rtsp_ext_client_debug, "rtspextclient", 0,
++ "GstRTSPExtClient");
++}
++
++static void
++gst_rtsp_ext_client_init (GstRTSPExtClient * client)
++{
++ GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (client);
++
++ client->priv = priv;
++ priv->resend_packets = 0;
++ priv->prev_max_seqnum = 0;
++ priv->prev_fraction_lost = 0;
++ priv->prev_max_packets_lost = 0;
++ priv->first_rtcp = FALSE;
++ priv->consecutive_low_bitrate_count = 0;
++ priv->tizen_retransmission_rtp_port = TIZEN_RETRANSMISSION_RTP_PORT_NONE;
++ priv->tizen_retransmission_rtcp_port = TIZEN_RETRANSMISSION_RTCP_PORT_NONE;
++ priv->tizen_fec_t_max = TIZEN_T_MAX_NONE;
++ priv->tizen_fec_p_max = TIZEN_P_MAX_NONE;
++ priv->tizen_latency_mode = GST_WFD_TIZEN_LATENCY_NONE;
++
++ GST_INFO_OBJECT (client, "Client is initialized");
++
++ return;
++}
++
++/* A client is finalized when the connection is broken */
++static void
++gst_rtsp_ext_client_finalize (GObject * obj)
++{
++ GstRTSPExtClient *client = GST_RTSP_EXT_CLIENT (obj);
++// GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (client);
++
++ GST_INFO ("finalize client %p", client);
++
++ G_OBJECT_CLASS (gst_rtsp_ext_client_parent_class)->finalize (obj);
++}
++
++/**
++ * gst_rtsp_ext_client_new:
++ *
++ * Create a new #GstRTSPExtClient instance.
++ *
++ * Returns: a new #GstRTSPExtClient
++ */
++GstRTSPExtClient *
++gst_rtsp_ext_client_new (void)
++{
++ GstRTSPExtClient *result;
++
++ result = g_object_new (GST_TYPE_RTSP_EXT_CLIENT, NULL);
++
++ return result;
++}
++
++static gboolean
++ext_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media,
++ GstRTSPStream * stream, GstRTSPContext * ctx)
++{
++ GstRTSPMediaExt* _media = NULL;
++ GstRTSPExtClient *_client = GST_RTSP_EXT_CLIENT (client);
++ GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (_client);
++
++ _media = GST_RTSP_MEDIA_EXT (media);
++
++ if (GST_IS_RTSP_MEDIA_EXT (_media)) {
++ if (_media != priv->media) {
++ GST_ERROR_OBJECT (client, "Different media!");
++ priv->media = _media;
++ }
++ }
++
++ return GST_RTSP_WFD_CLIENT_CLASS (gst_rtsp_ext_client_parent_class)->
++ configure_client_media (client, media, stream, ctx);
++}
++
++static gboolean
++_set_venc_bitrate (GstRTSPWFDClient * client, gint bitrate)
++{
++ 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_venc_bitrate (factory, bitrate);
++ 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
++_get_venc_bitrate (GstRTSPWFDClient * client, gint * bitrate)
++{
++ 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_get_venc_bitrate (factory, bitrate);
++ 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
++_get_config_bitrate (GstRTSPWFDClient * client, guint32 * min, guint32 * max)
++{
++ 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_get_config_bitrate (factory, min, max);
++ 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
++_bitrate_config (GstRTSPWFDClient * client, gint bitrate, guint32 min_bitrate)
++{
++ GstRTSPExtClient *_client = GST_RTSP_EXT_CLIENT (client);
++ GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (_client);
++ gint prev_bitrate;
++
++ _get_venc_bitrate (client, &prev_bitrate);
++
++ if (prev_bitrate != bitrate) {
++ _set_venc_bitrate (client, bitrate);
++ GST_INFO_OBJECT (client, "[UDP] New Bitrate value [%d]", bitrate);
++ }
++
++ if (prev_bitrate == min_bitrate && prev_bitrate == bitrate)
++ priv->consecutive_low_bitrate_count++;
++ else
++ priv->consecutive_low_bitrate_count = 0;
++
++ if (priv->consecutive_low_bitrate_count >= UNSTABLE_NETWORK_INTERVAL) {
++ /* Network congestion happens. Add logic for popup warning or something else */
++ GST_WARNING_OBJECT (client, "Network unstable");
++ priv->consecutive_low_bitrate_count = 0;
++ }
++
++ return TRUE;
++}
++
++static void
++handle_ext_stats (GstRTSPWFDClient * client, GstStructure * stats)
++{
++ GstRTSPExtClient *_client = GST_RTSP_EXT_CLIENT (client);
++ GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (_client);
++ guint latest_resend_packets = 0;
++
++ g_return_if_fail (priv != NULL);
++
++ latest_resend_packets = gst_rtsp_media_ext_get_resent_packets (priv->media);
++
++ GST_INFO_OBJECT (client, "Re-sent RTP packets : %d", latest_resend_packets);
++
++ /* calculation to decide bitrate */
++ {
++ static gint32 next_k = 40;
++ static gint32 next_p = 0;
++ guint32 min_bitrate = 0;
++ guint32 max_bitrate = 0;
++ guint fraction_lost = 0;
++ guint max_seqnum = 0;
++ gint packetslost;
++ gint bitrate = 0;
++ gint temp_fraction_lost = 0;
++ gint statistics_fraction_lost = 0;
++ gfloat thretholdValue = 0;
++ static gint fraction_lost_MA = 0;
++
++ gst_structure_get_uint (stats, "rb-fractionlost", &fraction_lost);
++ gst_structure_get_uint (stats, "rb-exthighestseq", &max_seqnum);
++ gst_structure_get_int (stats, "rb-packetslost", &packetslost);
++
++ _get_venc_bitrate (client, &bitrate);
++ GST_INFO_OBJECT (client, "[UDP] Current Bitrate value [%d]", bitrate);
++
++ _get_config_bitrate (client, &min_bitrate, &max_bitrate);
++ GST_INFO_OBJECT (client, "[UDP] min [%d], max [%d]", min_bitrate,
++ max_bitrate);
++
++ if (priv->resend_packets == latest_resend_packets)
++ fraction_lost = 0;
++ priv->resend_packets = latest_resend_packets;
++
++ if (priv->prev_max_seqnum == max_seqnum)
++ goto config;
++
++ if (priv->first_rtcp == FALSE) {
++ GST_DEBUG_OBJECT (client, "Ignoring first receiver report");
++ priv->prev_fraction_lost = 0;
++ priv->prev_max_packets_lost = packetslost;
++ priv->prev_max_seqnum = max_seqnum;
++ fraction_lost_MA = 0;
++ priv->first_rtcp = TRUE;
++ return;
++ }
++
++ if (priv->prev_fraction_lost == 0)
++ thretholdValue = 1.0;
++ else
++ thretholdValue = 0.8;
++
++ if (fraction_lost > 0) {
++ temp_fraction_lost = fraction_lost * 100 / 256;
++ GST_DEBUG_OBJECT (client, "fraction lost from sink RR [%d]",
++ temp_fraction_lost);
++ } else {
++ if ((max_seqnum > priv->prev_max_seqnum)
++ && (packetslost > priv->prev_max_packets_lost))
++ temp_fraction_lost =
++ (((packetslost - priv->prev_max_packets_lost) * 100) / (max_seqnum -
++ priv->prev_max_seqnum));
++ GST_DEBUG_OBJECT (client, "fraction lost calculated [%d]",
++ temp_fraction_lost);
++ }
++ statistics_fraction_lost =
++ (gint) (temp_fraction_lost * thretholdValue +
++ priv->prev_fraction_lost * (1 - thretholdValue));
++ fraction_lost_MA =
++ (fraction_lost_MA * 7 + statistics_fraction_lost * 5) / 8;
++
++ if (fraction_lost_MA > 100)
++ fraction_lost_MA = 100;
++
++ GST_DEBUG_OBJECT (client,
++ "statistics fraction lost = %d, fraction lost MA = %d",
++ statistics_fraction_lost, fraction_lost_MA);
++
++ if (temp_fraction_lost > 0) {
++ guint32 temp_change_bandwith_amount = 0;
++ gint32 fec_step = 0;
++
++ if (statistics_fraction_lost >= 5) {
++ temp_change_bandwith_amount = max_bitrate - min_bitrate;
++ fec_step = 10;
++ } else if (temp_fraction_lost >= 3) {
++ temp_change_bandwith_amount = (max_bitrate - min_bitrate) / 2;
++ fec_step = 5;
++ } else {
++ temp_change_bandwith_amount = (max_bitrate - min_bitrate) / 4;
++ fec_step = 3;
++ }
++
++ GST_DEBUG_OBJECT (client,
++ "LOSS case, statistics fraction lost = %d percent, temp change"
++ "bandwith amount = %d bit", statistics_fraction_lost,
++ temp_change_bandwith_amount);
++
++ if (next_p >= 100)
++ next_k -= fec_step;
++ else
++ next_p += fec_step;
++
++ if (next_k < 10)
++ next_k = 10;
++
++ if (next_p > 100)
++ next_p = 100;
++
++ if (bitrate <= min_bitrate) {
++ bitrate = min_bitrate;
++ priv->prev_fraction_lost = statistics_fraction_lost;
++ priv->prev_max_packets_lost = packetslost;
++ goto config;
++ }
++
++ bitrate -= temp_change_bandwith_amount;
++
++ if (bitrate < min_bitrate)
++ bitrate = min_bitrate;
++
++ } else if (0 == temp_fraction_lost && fraction_lost_MA < 1) {
++ gint32 fec_step = 0;
++
++ if (0 == priv->prev_fraction_lost) {
++ bitrate += 512 * 1024;
++ fec_step = 10;
++ } else {
++ bitrate += 100 * 1024;
++ fec_step = 5;
++ }
++
++ if (bitrate > max_bitrate)
++ bitrate = max_bitrate;
++
++ if (next_p <= 0)
++ next_k += fec_step;
++ else
++ next_p -= fec_step;
++
++ if (next_k > 100)
++ next_k = 100;
++
++ if (next_p < 0)
++ next_p = 0;
++
++ if (bitrate >= max_bitrate) {
++ GST_DEBUG_OBJECT (client, "bitrate can not be increased");
++ bitrate = max_bitrate;
++ priv->prev_fraction_lost = statistics_fraction_lost;
++ priv->prev_max_seqnum = max_seqnum;
++ priv->prev_max_packets_lost = packetslost;
++ goto config;
++ }
++
++ }
++
++ priv->prev_fraction_lost = statistics_fraction_lost;
++ priv->prev_max_seqnum = max_seqnum;
++ priv->prev_max_packets_lost = packetslost;
++
++ GST_INFO_OBJECT (client, "final bitrate is %d", bitrate);
++
++ config:
++ _bitrate_config (client, bitrate, min_bitrate);
++ gst_rtsp_media_ext_set_next_param (priv->media, next_k, next_p);
++ }
++}
++
++static gchar *
++handle_ext_m3_req_msg (GstRTSPWFDClient * client, gchar * data)
++{
++ gchar *tmp = NULL;
++ gchar *sink_user_agent = NULL;
++ GstWFDExtMessage *msg = NULL;
++ GstWFDResult wfd_res = GST_WFD_EINVAL;
++ gboolean is_appended = FALSE;
++
++ g_return_val_if_fail (client != NULL, NULL);
++ g_return_val_if_fail (data != NULL, NULL);
++
++ sink_user_agent = gst_rtsp_wfd_client_get_sink_user_agent (client);
++
++ if (sink_user_agent && strstr (sink_user_agent, TIZEN_USER_AGENT)) {
++
++ GST_INFO_OBJECT (client,
++ "Setting tizen extended features on wfd message...");
++
++ wfd_res = gst_wfd_ext_message_new (&msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to create wfd message...");
++ goto error;
++ }
++
++ wfd_res = gst_wfd_ext_message_init (msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to init wfd message...");
++ goto error;
++ }
++
++ GST_INFO_OBJECT (client,
++ "Setting tizen extended features on wfd message...");
++
++ wfd_res = gst_wfd_ext_message_set_tizen_retransmission (msg, 0, 0);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set tizen retransmission on wfd message...");
++ goto error;
++ }
++
++ wfd_res = gst_wfd_ext_message_set_tizen_fec (msg, 0, 0);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to set tizen fec on wfd message...");
++ goto error;
++ }
++
++ wfd_res = gst_wfd_ext_message_set_tizen_latency_mode (msg, 0);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set tizen latency mode on wfd message...");
++ goto error;
++ }
++
++ tmp = gst_wfd_ext_message_param_names_as_text (msg);
++ if (tmp) {
++ data = g_strconcat (data, tmp, NULL);
++ g_free (tmp);
++ is_appended = TRUE;
++ } else {
++ GST_ERROR_OBJECT (client,
++ "Failed to gst_wfd_ext_message_param_names_as_text");
++ goto error;
++ }
++ }
++ if (msg != NULL)
++ gst_wfd_ext_message_free (msg);
++
++ if (sink_user_agent != NULL)
++ g_free (sink_user_agent);
++
++ if (is_appended == FALSE)
++ return NULL;
++ else
++ return data;
++
++error:
++ if (msg != NULL)
++ gst_wfd_ext_message_free (msg);
++
++ if (sink_user_agent != NULL)
++ g_free (sink_user_agent);
++
++ return NULL;
++}
++
++static void
++handle_ext_m3_res_msg (GstRTSPWFDClient * client, gchar * data)
++{
++ GstWFDExtMessage *msg = NULL;
++ GstRTSPExtClientPrivate *ext_priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (client);
++ GstWFDResult wfd_res = GST_WFD_EINVAL;
++
++ g_return_if_fail (ext_priv != NULL);
++ g_return_if_fail (data != NULL);
++
++ wfd_res = gst_wfd_ext_message_new (&msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to create wfd message...");
++ goto error;
++ }
++
++ wfd_res = gst_wfd_ext_message_init (msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to init wfd message...");
++ goto error;
++ }
++
++ wfd_res =
++ gst_wfd_ext_message_parse_buffer ((const guint8 *) data, strlen (data),
++ msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to parse buffer...");
++ goto error;
++ }
++
++ /* Get tizen extended features from WFD message. */
++ if (msg->tizen_retransmission) {
++
++ guint rtp_port = TIZEN_RETRANSMISSION_RTP_PORT_NONE;
++ guint rtcp_port = TIZEN_RETRANSMISSION_RTCP_PORT_NONE;
++ wfd_res =
++ gst_wfd_ext_message_get_tizen_retransmission (msg, &rtp_port,
++ &rtcp_port);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to get tizen retransmission from wfd message...");
++ goto error;
++ }
++
++ if (rtp_port >= MIN_PORT_NUM && rtp_port <= MAX_PORT_NUM)
++ ext_priv->tizen_retransmission_rtp_port = rtp_port;
++
++ if (rtcp_port >= MIN_PORT_NUM && rtcp_port <= MAX_PORT_NUM)
++ ext_priv->tizen_retransmission_rtcp_port = rtcp_port;
++
++ GST_DEBUG_OBJECT (client, "Tizen retransmission rtp_port[%d] rtcp_port[%d]",
++ ext_priv->tizen_retransmission_rtp_port,
++ ext_priv->tizen_retransmission_rtcp_port);
++ }
++
++ if (msg->tizen_fec) {
++
++ guint fec_t_max = TIZEN_T_MAX_NONE;
++ guint fec_p_max = TIZEN_P_MAX_NONE;
++
++ wfd_res = gst_wfd_ext_message_get_tizen_fec (msg, &fec_t_max, &fec_p_max);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to get tizen fec from wfd message...");
++ goto error;
++ }
++
++ if (fec_t_max >= MIN_FEC_T_NUM && fec_t_max <= MAX_FEC_T_NUM)
++ ext_priv->tizen_fec_t_max = fec_t_max;
++
++ if (fec_p_max >= MIN_FEC_P_NUM && fec_p_max <= MAX_FEC_P_NUM)
++ ext_priv->tizen_fec_p_max = fec_p_max;
++
++ GST_DEBUG_OBJECT (client, "Tizen fec t_max[%d] p_max[%d]",
++ ext_priv->tizen_fec_t_max, ext_priv->tizen_fec_p_max);
++ }
++
++ if (msg->tizen_latency_mode) {
++ wfd_res =
++ gst_wfd_ext_message_get_tizen_latency_mode (msg,
++ &ext_priv->tizen_latency_mode);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to get tizen latency mode on wfd message...");
++ goto error;
++ }
++ GST_DEBUG_OBJECT (client, "Tizen latency mode[%d]",
++ ext_priv->tizen_latency_mode);
++ }
++
++ if (msg != NULL)
++ gst_wfd_ext_message_free (msg);
++
++ return;
++error:
++
++ if (msg != NULL)
++ gst_wfd_ext_message_free (msg);
++
++ return;
++}
++
++static void
++media_ext_constructed (GstRTSPMediaFactory * factory, GstRTSPMedia * media,
++ GstRTSPExtClient * client)
++{
++ GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (client);
++ g_return_if_fail (priv != NULL);
++
++ priv->media = GST_RTSP_MEDIA_EXT (media);
++
++ if (priv->tizen_retransmission_rtp_port != TIZEN_RETRANSMISSION_RTP_PORT_NONE
++ && priv->tizen_retransmission_rtcp_port !=
++ TIZEN_RETRANSMISSION_RTCP_PORT_NONE) {
++ GST_DEBUG_OBJECT (client, "Tizen retransmission rtp_port[%d] rtcp_port[%d]",
++ priv->tizen_retransmission_rtp_port,
++ priv->tizen_retransmission_rtcp_port);
++ gst_rtsp_media_ext_set_extended_mode (priv->media, MEDIA_EXT_MODE_RESEND);
++ gst_rtsp_media_ext_set_retrans_port (priv->media,
++ priv->tizen_retransmission_rtp_port);
++ }
++ if (priv->tizen_fec_t_max != TIZEN_T_MAX_NONE
++ && priv->tizen_fec_p_max != TIZEN_P_MAX_NONE) {
++ GST_DEBUG_OBJECT (client, "Tizen fec t_max[%d] p_max[%d]",
++ priv->tizen_fec_t_max, priv->tizen_fec_p_max);
++ gst_rtsp_media_ext_set_extended_mode (priv->media, MEDIA_EXT_MODE_FEC);
++ gst_rtsp_media_ext_set_fec_value (priv->media, priv->tizen_fec_t_max,
++ priv->tizen_fec_p_max);
++ }
++ if (priv->tizen_latency_mode != GST_WFD_TIZEN_LATENCY_NONE) {
++ GST_DEBUG_OBJECT (client, "Tizen latency mode[%d]",
++ priv->tizen_latency_mode);
++ gst_rtsp_media_ext_set_latency_mode (priv->media, priv->tizen_latency_mode);
++ }
++}
++
++static void
++gst_wfd_ext_listen_media_constructed (GstRTSPWFDClient * client)
++{
++ GstRTSPMediaFactory *factory = NULL;
++ GstRTSPMountPoints *mount_points = NULL;
++ gchar *path = NULL;
++ gint matched = 0;
++ GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client);
++
++ GstRTSPExtClient *_client = GST_RTSP_EXT_CLIENT (client);
++
++ if (!(mount_points = gst_rtsp_client_get_mount_points (parent_client))) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set negotiated resolution: no mount points...");
++ goto no_mount_points;
++ }
++
++ path = g_strdup (WFD_MOUNT_POINT);
++ if (!path) {
++ 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...");
++ goto no_factory;
++ }
++
++ g_signal_connect (factory, "media-constructed",
++ (GCallback) media_ext_constructed, _client);
++
++no_factory:
++ g_free (path);
++no_path:
++ g_object_unref (mount_points);
++no_mount_points:
++ return;
++}
++
++static void
++handle_ext_set_param_msg (GstRTSPWFDClient * client, gchar * data)
++{
++ GstRTSPExtClientPrivate *priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (client);
++
++ g_return_if_fail (priv != NULL);
++ g_return_if_fail (data != NULL);
++
++ return;
++}
++
++static gchar *
++handle_ext_m4_req_msg (GstRTSPWFDClient * client, gchar * data)
++{
++ GstWFDExtMessage *msg = NULL;
++ gchar *tmp = NULL;
++ GstRTSPExtClientPrivate *ext_priv = GST_RTSP_EXT_CLIENT_GET_PRIVATE (client);
++ GstWFDResult wfd_res = GST_WFD_EINVAL;
++
++ g_return_val_if_fail (ext_priv != NULL, NULL);
++ g_return_val_if_fail (data != NULL, NULL);
++
++ wfd_res = gst_wfd_ext_message_new (&msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to create wfd message...");
++ goto error;
++ }
++
++ wfd_res = gst_wfd_ext_message_init (msg);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to init wfd message...");
++ goto error;
++ }
++
++ GST_INFO_OBJECT (client, "Setting extended features on wfd message...");
++
++ if (ext_priv->tizen_retransmission_rtp_port !=
++ TIZEN_RETRANSMISSION_RTP_PORT_NONE
++ && ext_priv->tizen_retransmission_rtcp_port !=
++ TIZEN_RETRANSMISSION_RTCP_PORT_NONE) {
++
++ wfd_res =
++ gst_wfd_ext_message_set_tizen_retransmission (msg,
++ ext_priv->tizen_retransmission_rtp_port,
++ ext_priv->tizen_retransmission_rtcp_port);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set tizen retransmission on wfd message...");
++ goto error;
++ }
++ GST_DEBUG_OBJECT (client, "Tizen retransmission rtp_port[%d] rtcp_port[%d]",
++ ext_priv->tizen_retransmission_rtp_port,
++ ext_priv->tizen_retransmission_rtcp_port);
++ }
++
++ if (ext_priv->tizen_fec_t_max != TIZEN_T_MAX_NONE
++ && ext_priv->tizen_fec_p_max != TIZEN_P_MAX_NONE) {
++
++ wfd_res =
++ gst_wfd_ext_message_set_tizen_fec (msg, ext_priv->tizen_fec_t_max,
++ ext_priv->tizen_fec_p_max);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to set tizen fec on wfd message...");
++ goto error;
++ }
++ GST_DEBUG_OBJECT (client, "Tizen fec t_max[%d] p_max[%d]",
++ ext_priv->tizen_fec_t_max, ext_priv->tizen_fec_p_max);
++ }
++
++ if (ext_priv->tizen_latency_mode != GST_WFD_TIZEN_LATENCY_NONE) {
++
++ wfd_res =
++ gst_wfd_ext_message_set_tizen_latency_mode (msg,
++ ext_priv->tizen_latency_mode);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set tizen latency mode on wfd message...");
++ goto error;
++ }
++ GST_DEBUG_OBJECT (client, "Tizen latency mode[%d]",
++ ext_priv->tizen_latency_mode);
++ }
++
++ tmp = gst_wfd_ext_message_as_text (msg);
++ if (tmp) {
++ data = g_strconcat (data, tmp, NULL);
++ g_free (tmp);
++ } else {
++ GST_ERROR_OBJECT (client, "Failed to gst_wfd_ext_message_as_text");
++ goto error;
++ }
++
++ if (msg != NULL)
++ gst_wfd_ext_message_free (msg);
++
++ gst_wfd_ext_listen_media_constructed (client);
++
++ return data;
++
++error:
++
++ if (msg != NULL)
++ gst_wfd_ext_message_free (msg);
++
++ return NULL;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
++ *
++ * 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 <gst/gst.h>
++#include <gst/rtsp/gstrtspconnection.h>
++
++#ifndef __GST_RTSP_EXT_CLIENT_H__
++#define __GST_RTSP_EXT_CLIENT_H__
++
++G_BEGIN_DECLS
++
++typedef struct _GstRTSPExtClient GstRTSPExtClient;
++typedef struct _GstRTSPExtClientClass GstRTSPExtClientClass;
++typedef struct _GstRTSPExtClientPrivate GstRTSPExtClientPrivate;
++
++#include "rtsp-mount-points.h"
++#include "rtsp-client-wfd.h"
++
++#define GST_TYPE_RTSP_EXT_CLIENT (gst_rtsp_ext_client_get_type ())
++#define GST_IS_RTSP_EXT_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_EXT_CLIENT))
++#define GST_IS_RTSP_EXT_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_EXT_CLIENT))
++#define GST_RTSP_EXT_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_EXT_CLIENT, GstRTSPExtClientClass))
++#define GST_RTSP_EXT_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_EXT_CLIENT, GstRTSPExtClient))
++#define GST_RTSP_EXT_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_EXT_CLIENT, GstRTSPExtClientClass))
++#define GST_RTSP_EXT_CLIENT_CAST(obj) ((GstRTSPExtClient*)(obj))
++#define GST_RTSP_EXT_CLIENT_CLASS_CAST(klass) ((GstRTSPExtClientClass*)(klass))
++
++/**
++ * GstRTSPExtClient:
++ *
++ * The client object represents the connection and its state with a client.
++ */
++struct _GstRTSPExtClient {
++ GstRTSPWFDClient parent;
++
++ /*< private >*/
++ GstRTSPExtClientPrivate *priv;
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++/**
++ * GstRTSPExtClientClass:
++ *
++ * The client class structure.
++ */
++struct _GstRTSPExtClientClass {
++ GstRTSPWFDClientClass parent_class;
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++GST_RTSP_SERVER_API
++GType gst_rtsp_ext_client_get_type (void);
++
++GST_RTSP_SERVER_API
++GstRTSPExtClient * gst_rtsp_ext_client_new (void);
++
++G_END_DECLS
++
++#endif /* __GST_RTSP_EXT_CLIENT_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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)
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <stdio.h>
++#include <string.h>
++#include <unistd.h>
++#include <sys/ioctl.h>
++#include <netinet/in.h>
++#include <netinet/tcp.h>
++#include <arpa/inet.h>
++
++#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;
++};
++
++typedef enum {
++ WFD_TS_UDP,
++ WFD_TS_TCP
++} WFDTSMode;
++
++typedef enum {
++ WFD_TS_REP_AUDIO,
++ WFD_TS_REP_VIDEO
++} WFDTSReportType;
++
++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;
++ guint8 video_codec;
++ 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 direct_streaming_supported;
++ gint direct_streaming_state;
++ guint8 direct_detected_video_codec;
++ guint8 direct_detected_audio_codec;
++
++ 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;
++
++ gchar *sink_user_agent;
++ guint ctrans_tcp;
++ guint cprofile_tcp;
++ guint clowertrans_tcp;
++ guint32 crtp_port0_tcp;
++ guint32 crtp_port1_tcp;
++ guint buf_len;
++ WFDTSMode ts_mode;
++ WFDTSReportType report_type;
++ GstRTSPWatch *datawatch;
++ guint datawatchid;
++ GstRTSPConnection *data_conn;
++ gchar *uristr;
++ GMutex tcp_send_lock;
++
++ /* enable or disable R2 features */
++ gboolean wfd2_mode;
++ gint wfd2_supported;
++ gboolean coupling_mode;
++
++ guint coupled_sink_status;
++ gchar *coupled_sink_address;
++ gboolean coupled_sink_supported;
++};
++
++#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_PLAYING_DONE,
++ SIGNAL_WFD_RTP_STATS,
++ SIGNAL_WFD_M3_REQ_MSG,
++ SIGNAL_WFD_M3_RES_MSG,
++ SIGNAL_WFD_M4_REQ_MSG,
++ SIGNAL_WFD_SET_PARAM_MSG,
++ 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, GstRTSPVersion version);
++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_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);
++
++static GstRTSPResult handle_M4_direct_streaming_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);
++
++ 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);
++
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_RTP_STATS] =
++ g_signal_new ("wfd-rtp-stats", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass,
++ wfd_rtp_stats), NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_NONE, 1, GST_TYPE_STRUCTURE);
++
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_M3_REQ_MSG] =
++ g_signal_new ("wfd-m3-request-msg", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass,
++ wfd_handle_m3_req_msg), NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_STRING, 1, G_TYPE_STRING);
++
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_M3_RES_MSG] =
++ g_signal_new ("wfd-m3-response-msg", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass,
++ wfd_handle_m3_res_msg), NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_NONE, 1, G_TYPE_STRING);
++
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_M4_REQ_MSG] =
++ g_signal_new ("wfd-m4-request-msg", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass,
++ wfd_handle_m4_req_msg), NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_STRING, 1, G_TYPE_STRING);
++
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_SET_PARAM_MSG] =
++ g_signal_new ("wfd-set-param-msg", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPWFDClientClass,
++ wfd_handle_set_param_msg), NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_NONE, 1, G_TYPE_STRING);
++
++ klass->wfd_options_request = wfd_options_request_done;
++ klass->wfd_get_param_request = wfd_get_param_request_done;
++ klass->configure_client_media = wfd_configure_client_media;
++
++ 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));
++
++ priv->direct_streaming_supported = FALSE;
++ priv->direct_streaming_state = 0;
++
++ priv->sink_user_agent = NULL;
++
++ priv->ts_mode = WFD_TS_UDP;
++ priv->report_type = WFD_TS_REP_AUDIO;
++
++ priv->wfd2_supported = 0;
++ priv->coupled_sink_address = NULL;
++
++ g_mutex_init (&priv->tcp_send_lock);
++ 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);
++
++ if (priv->sink_user_agent) {
++ g_free (priv->sink_user_agent);
++ priv->sink_user_agent = NULL;
++ }
++
++ g_mutex_clear (&priv->keep_alive_lock);
++ g_mutex_clear (&priv->stats_lock);
++ g_mutex_clear (&priv->tcp_send_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);
++ g_signal_emit (client, gst_rtsp_client_wfd_signals[SIGNAL_WFD_RTP_STATS], 0,
++ stats);
++}
++
++static gboolean
++wfd_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media,
++ GstRTSPStream * stream, GstRTSPContext * ctx)
++{
++ if (media) {
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ priv->media = media;
++ }
++ 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_preferred_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 guint
++wfd_get_preferred_video_codec (guint8 srcVideoCodec, guint sinkVideoCodec)
++{
++ int i = 0;
++ guint codec = 0;
++ for (i = 0; i < 8; i++) {
++ if (((sinkVideoCodec << i) & 0x80)
++ && ((srcVideoCodec << i) & 0x80)) {
++ codec = (0x01 << (7 - i));
++ break;
++ }
++ }
++ return codec;
++}
++
++static guint64
++wfd_get_preferred_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);
++
++ g_signal_emit (client,
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_PLAYING_DONE], 0, NULL);
++}
++
++static gboolean
++do_send_data (GstBuffer * buffer, guint8 channel, GstRTSPClient * client)
++{
++ GstRTSPMessage message = { 0 };
++ GstRTSPResult res = GST_RTSP_OK;
++ GstMapInfo map_info;
++ guint8 *data;
++ guint usize;
++
++ gst_rtsp_message_init_data (&message, channel);
++
++ /* FIXME, need some sort of iovec RTSPMessage here */
++ if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ))
++ return FALSE;
++
++ gst_rtsp_message_take_body (&message, map_info.data, map_info.size);
++
++ g_mutex_lock (&(GST_RTSP_WFD_CLIENT (client)->priv->tcp_send_lock));
++
++ gst_rtsp_watch_send_message (GST_RTSP_WFD_CLIENT (client)->priv->datawatch, &message, NULL);
++
++ g_mutex_unlock (&(GST_RTSP_WFD_CLIENT (client)->priv->tcp_send_lock));
++
++ gst_rtsp_message_steal_body (&message, &data, &usize);
++ gst_buffer_unmap (buffer, &map_info);
++
++ gst_rtsp_message_unset (&message);
++
++ return res == GST_RTSP_OK;
++}
++static GstRTSPResult
++message_received (GstRTSPWatch * watch, GstRTSPMessage * message,
++ gpointer user_data)
++{
++ return gst_rtsp_client_handle_message (GST_RTSP_CLIENT (user_data), message);
++}
++
++static GstRTSPResult
++message_sent (GstRTSPWatch * watch, guint cseq, gpointer user_data)
++{
++ GstRTSPClient *client;
++
++ client = GST_RTSP_CLIENT (user_data);
++ if(client == NULL)
++ return GST_RTSP_ERROR;
++ return GST_RTSP_OK;
++}
++
++static GstRTSPResult
++error (GstRTSPWatch * watch, GstRTSPResult result, gpointer user_data)
++{
++ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
++ gchar *str;
++
++ str = gst_rtsp_strresult (result);
++ GST_INFO ("client %p: received an error %s", client, str);
++ g_free (str);
++
++ return GST_RTSP_OK;
++}
++static GstRTSPResult
++closed_tcp (GstRTSPWatch * watch, gpointer user_data)
++{
++ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
++
++ GST_INFO ("client %p: connection closed", client);
++
++ return GST_RTSP_OK;
++}
++
++static GstRTSPResult
++error_full_tcp (GstRTSPWatch * watch, GstRTSPResult result,
++ GstRTSPMessage * message, guint id, gpointer user_data)
++{
++ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
++ gchar *str;
++
++ str = gst_rtsp_strresult (result);
++ GST_INFO
++ ("client %p: received an error %s when handling message %p with id %d",
++ client, str, message, id);
++ g_free (str);
++
++ return GST_RTSP_OK;
++}
++
++static GstRTSPWatchFuncs watch_funcs_tcp = {
++ message_received,
++ message_sent,
++ closed_tcp,
++ error,
++ NULL,
++ NULL,
++ error_full_tcp,
++ NULL
++};
++static void
++client_watch_notify_tcp (GstRTSPClient * client)
++{
++ GST_INFO ("client %p: watch destroyed", client);
++ GST_RTSP_WFD_CLIENT (client)->priv->datawatch = NULL;
++ GST_RTSP_WFD_CLIENT (client)->priv->data_conn = NULL;
++}
++
++static GstRTSPResult
++new_tcp (GstRTSPWFDClient * client)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPConnection *conn = NULL;
++ GstRTSPConnection *parent_conn = NULL;
++ GstRTSPUrl *url;
++ GSource *source;
++ GMainContext *context;
++ int conn_retry_remained = 10;
++ int bsize = -1;
++ GError *err = NULL;
++
++ /* client's address */
++ int ret;
++ GSocket *tcp_socket = NULL;
++ GSocketAddress *tcp_socket_addr = NULL;
++
++ /* Get the client connection details */
++ parent_conn = gst_rtsp_client_get_connection (GST_RTSP_CLIENT (client));
++ url = gst_rtsp_connection_get_url (parent_conn);
++ if(!url)
++ return GST_RTSP_ERROR;
++
++ gst_rtsp_url_set_port (url, client->priv->crtp_port0_tcp);
++
++ GST_INFO ("create new connection %p ip %s:%d", client, url->host, url->port);
++
++ /* create a TCP/IP socket */
++ if ((tcp_socket = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP, NULL)) == NULL) {
++ GST_ERROR ("cannot create socket");
++ return GST_RTSP_ERROR;
++ }
++
++ /* allow immediate reuse of the port */
++ ret = g_socket_set_option (tcp_socket, SOL_SOCKET, SO_REUSEADDR, TRUE, NULL);
++ if (ret == 0) {
++ GST_ERROR ("cannot change socket options");
++ goto failed;
++ }
++
++ /* bind the socket to our source address */
++ tcp_socket_addr = g_inet_socket_address_new_from_string (url->host, url->port);
++ if (!tcp_socket_addr) {
++ GST_ERROR ("tcp_socket_addr is failed");
++ goto failed;
++ }
++
++ g_socket_set_blocking (tcp_socket, FALSE);
++
++ while (!g_socket_connect (tcp_socket, tcp_socket_addr, NULL, &err)) {
++ GST_ERROR ("Connection failed... Try again...");
++ if (err) {
++ GST_ERROR (" error: [%s]", err->message);
++ g_error_free (err);
++ err = NULL;
++ }
++
++ if (conn_retry_remained-- == 0) {
++ GST_ERROR ("Failed to connection finally.");
++ goto failed;
++ }
++
++ usleep (100000);
++ }
++
++ res = gst_rtsp_connection_create_from_socket (tcp_socket, url->host, url->port, NULL, &conn);
++ if (res < 0) {
++ GST_ERROR ("gst_rtsp_connection_create_from_socket function is failed");
++ goto failed;
++ }
++
++ /* Set send buffer size to 1024000 */
++ if (g_socket_set_option (tcp_socket , SOL_SOCKET, SO_SNDBUF, 1024000, NULL))
++ GST_DEBUG_OBJECT (client, "Set send buf size : %d\n", bsize);
++ else
++ GST_ERROR_OBJECT (client, "SO_SNDBUF setsockopt failed");
++
++ /* Get send buffer size */
++ if (g_socket_get_option (tcp_socket , SOL_SOCKET, SO_SNDBUF, &bsize, &err)) {
++ GST_DEBUG_OBJECT (client, "Get send buf size : %d\n", bsize);
++ } else {
++ GST_ERROR_OBJECT (client, "SO_SNDBUF getsockopt failed");
++ if (err) {
++ GST_ERROR_OBJECT (client," error: [%s]", err->message);
++ g_error_free (err);
++ err = NULL;
++ }
++ }
++
++ /* Set TCP no delay */
++ if (g_socket_set_option (tcp_socket , IPPROTO_TCP, TCP_NODELAY, TRUE, NULL))
++ GST_DEBUG_OBJECT (client, "TCP NO DELAY");
++ else
++ GST_ERROR_OBJECT (client, "TCP_NODELAY setsockopt failed");
++
++ client->priv->data_conn = conn;
++
++ /* create watch for the connection and attach */
++ client->priv->datawatch = gst_rtsp_watch_new (client->priv->data_conn, &watch_funcs_tcp, client, (GDestroyNotify) client_watch_notify_tcp);
++ GST_DEBUG_OBJECT (client, "data watch : %p", client->priv->datawatch);
++ /* find the context to add the watch */
++ if ((source = g_main_current_source ()))
++ context = g_source_get_context (source);
++ else
++ context = NULL;
++
++ GST_DEBUG (" source = %p", source);
++ GST_INFO ("attaching to context %p", context);
++ client->priv->datawatchid = gst_rtsp_watch_attach (client->priv->datawatch, context);
++ gst_rtsp_watch_unref (client->priv->datawatch);
++ g_object_unref (tcp_socket_addr);
++ return res;
++
++failed:
++ g_object_unref (tcp_socket_addr);
++ g_object_unref (tcp_socket);
++
++ return GST_RTSP_ERROR;
++}
++
++static void
++do_keepalive (GstRTSPSession * session)
++{
++ GST_INFO ("keep session %p alive", session);
++ gst_rtsp_session_touch (session);
++}
++static void
++map_transport (GstRTSPWFDClient * client, GstRTSPTransport * ct)
++{
++ switch(client->priv->ctrans) {
++ case GST_WFD_RTSP_TRANS_RTP:
++ ct->trans = GST_RTSP_TRANS_RTP;
++ break;
++ case GST_WFD_RTSP_TRANS_RDT:
++ ct->trans = GST_RTSP_TRANS_RDT;
++ break;
++ default:
++ ct->trans = GST_RTSP_TRANS_UNKNOWN;
++ break;
++ }
++ switch(client->priv->cprofile) {
++ case GST_WFD_RTSP_PROFILE_AVP:
++ ct->profile = GST_RTSP_PROFILE_AVP;
++ break;
++ case GST_WFD_RTSP_PROFILE_SAVP:
++ ct->profile = GST_RTSP_PROFILE_SAVP;
++ break;
++ default:
++ ct->profile = GST_RTSP_PROFILE_UNKNOWN;
++ break;
++ }
++ switch(client->priv->clowertrans) {
++ case GST_WFD_RTSP_LOWER_TRANS_UDP:
++ ct->lower_transport = GST_RTSP_LOWER_TRANS_UDP;
++ break;
++ case GST_WFD_RTSP_LOWER_TRANS_UDP_MCAST:
++ ct->lower_transport = GST_RTSP_LOWER_TRANS_UDP_MCAST;
++ break;
++ case GST_WFD_RTSP_LOWER_TRANS_TCP:
++ ct->lower_transport = GST_RTSP_LOWER_TRANS_TCP;
++ break;
++ case GST_WFD_RTSP_LOWER_TRANS_HTTP:
++ ct->lower_transport = GST_RTSP_LOWER_TRANS_HTTP;
++ break;
++ default:
++ ct->lower_transport = GST_RTSP_LOWER_TRANS_UNKNOWN;
++ break;
++ }
++
++ if (client->priv->ts_mode == WFD_TS_TCP)
++ ct->lower_transport = GST_RTSP_LOWER_TRANS_TCP;
++}
++
++static GstRTSPResult
++handle_ts_response (GstRTSPWFDClient * client, GstRTSPContext * ctx)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPTransport *ct;
++ GstRTSPConnection *conn;
++ GstRTSPUrl *url = NULL;
++ GList *t = NULL;
++ GstRTSPStreamTransport *tr = NULL;
++ GPtrArray *ta = NULL;
++
++ ta = g_ptr_array_new();
++
++ t = client->priv->transports;
++ tr = GST_RTSP_STREAM_TRANSPORT (t->data);
++ g_ptr_array_add (ta, t->data);
++
++ gst_rtsp_stream_transport_set_callbacks (tr, NULL, NULL, NULL, NULL);
++ gst_rtsp_stream_transport_set_keepalive (tr, NULL, ctx->session, NULL);
++
++ gst_rtsp_transport_new (&ct);
++
++ map_transport (client, ct);
++
++ if (ct->trans != GST_RTSP_TRANS_RTP || ct->profile != GST_RTSP_PROFILE_AVP) {
++ GST_WARNING_OBJECT (client, "Trans or profile is wrong");
++ goto error;
++ }
++ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_HTTP ||
++ ct->lower_transport == GST_RTSP_LOWER_TRANS_UNKNOWN) {
++ GST_WARNING_OBJECT (client, "Lowertrans is wrong");
++ goto error;
++ }
++
++ if (client->priv->ts_mode == WFD_TS_UDP) {
++ g_print ("\nSwitched to UDP !!!\n");
++ /* Free any previous TCP connection */
++ if(client->priv->data_conn)
++ {
++ gst_rtsp_connection_close (client->priv->data_conn);
++ gst_rtsp_connection_free(client->priv->data_conn);
++ if (client->priv->datawatch) {
++ g_source_destroy ((GSource *)client->priv->datawatch);
++ }
++ }
++ conn = gst_rtsp_client_get_connection (GST_RTSP_CLIENT (client));
++ url = gst_rtsp_connection_get_url (conn);
++ gst_rtsp_url_set_port (url, client->priv->crtp_port0);
++ ct->destination = g_strdup (url->host);
++ ct->client_port.min = client->priv->crtp_port0;
++ if(client->priv->crtp_port1 == 0)
++ ct->client_port.max = client->priv->crtp_port0 + 1;
++ else ct->client_port.max = client->priv->crtp_port1;
++ } else if (client->priv->ts_mode == WFD_TS_TCP) {
++ res = new_tcp(client);
++ if(res != GST_RTSP_OK)
++ goto error;
++
++ conn = gst_rtsp_client_get_connection (GST_RTSP_CLIENT (client));
++ url = gst_rtsp_connection_get_url (conn);
++ ct->destination = g_strdup (url->host);
++ ct->client_port.min = client->priv->crtp_port0_tcp;
++ if(client->priv->crtp_port1_tcp == 0)
++ ct->client_port.max = client->priv->crtp_port0_tcp + 1;
++ else ct->client_port.max = client->priv->crtp_port1_tcp;
++ }
++
++ gst_rtsp_stream_transport_set_transport (tr, ct);
++
++ GST_DEBUG ("client %p: linking transport", client);
++ if (client->priv->ts_mode == WFD_TS_TCP) {
++ g_print ("\nSwitched to TCP !!!\n");
++ gst_rtsp_stream_transport_set_callbacks (tr, (GstRTSPSendFunc) do_send_data,
++ (GstRTSPSendFunc) do_send_data, client, NULL);
++ }
++ else if(client->priv->ts_mode == WFD_TS_UDP ) {
++ g_print ("\nSwitched to UDP !!!\n");
++ /* configure keepalive for this transport */
++ gst_rtsp_stream_transport_set_keepalive (tr, (GstRTSPKeepAliveFunc) do_keepalive, ctx->session, NULL);
++ gst_rtsp_stream_transport_set_callbacks (tr, NULL, NULL, client, NULL);
++ }
++
++ gst_rtsp_media_set_state (client->priv->media, GST_STATE_PLAYING, ta);
++
++ g_ptr_array_free (ta, FALSE);
++
++ return res;
++
++error:
++ gst_rtsp_transport_free (ct);
++ g_ptr_array_free (ta, FALSE);
++ return GST_RTSP_ERROR;
++}
++
++static void
++handle_wfd_response (GstRTSPClient * client, GstRTSPContext * ctx)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ guint8 *data = NULL;
++ guint size = 0;
++ GstWFDResult wfd_res;
++ GstWFDMessage *msg = NULL;
++
++ 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;
++ }
++
++ if (priv->sink_user_agent == NULL) {
++ gchar *user_agent = NULL;
++ res = gst_rtsp_message_get_header (ctx->response, GST_RTSP_HDR_USER_AGENT,
++ &user_agent, 0);
++ if (res == GST_RTSP_OK) {
++ priv->sink_user_agent = g_strdup (user_agent);
++ GST_INFO_OBJECT (_client, "sink user_agent : %s", priv->sink_user_agent);
++ } else {
++ GST_INFO_OBJECT (_client, "user_agent is NULL and user_agent is optional.");
++ }
++ }
++
++ /* 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) {
++ /* 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_ERROR_OBJECT (client, "M3 response server side message body: %s",
++ gst_wfd_message_as_text (msg));
++
++ /* Get the audio formats supported by WFDSink */
++ if (msg->wfd2_audio_codecs && msg->wfd2_audio_codecs->count > 0) {
++ priv->wfd2_mode = TRUE;
++ wfd_res =
++ gst_wfd_message_get_supported_wfd2_audio_codec (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;
++ }
++ } else if (msg->audio_codecs && msg->audio_codecs->count > 0) {
++ priv->wfd2_mode = FALSE;
++ 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;
++ }
++ }
++
++ if (msg->direct_video_formats) {
++ priv->direct_streaming_supported = TRUE;
++ }
++
++ /* Get the Video formats supported by WFDSink */
++ if (msg->video_formats && msg->video_formats->count > 0) {
++ 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_preferred_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 preferred RTP ports...");
++ goto error;
++ }
++ }
++ if (msg->tcp_ports) {
++ /* Get the TCP ports preferred by WFDSink */
++ wfd_res =
++ gst_wfd_message_get_preferred_tcp_ports (msg, &priv->ctrans_tcp,
++ &priv->cprofile_tcp, &priv->clowertrans_tcp, &priv->crtp_port0_tcp,
++ &priv->crtp_port1_tcp);
++ if (wfd_res != GST_WFD_OK) {
++ GST_WARNING_OBJECT (client,
++ "Failed to get wfd preferred RTP ports...");
++ goto error;
++ }
++ }
++
++ if (msg->buf_len) {
++ wfd_res =
++ gst_wfd_message_get_buffer_length (msg, &priv->buf_len);
++ if (wfd_res != GST_WFD_OK) {
++ GST_WARNING_OBJECT (client,
++ "Failed to get wfd preferred 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");
++ }
++ }
++ /* Release allocated memory */
++ g_free (edid_payload);
++ }
++
++ 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_M3_RES_MSG], 0, data);
++
++ g_signal_emit (_client,
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_GET_PARAMETER_REQUEST], 0,
++ ctx);
++ } else {
++ if (g_strrstr((char *)data, "wfd2_buffer_len")) {
++ GST_DEBUG_OBJECT (_client, "Get TS message responce");
++
++ /* Parse TS 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);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to parse wfd message...");
++ goto error;
++ }
++
++ wfd_res = gst_wfd_message_get_buffer_length (msg, &_client->priv->buf_len);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to parse wfd message...");
++ goto error;
++ }
++
++ if (GST_RTSP_OK != handle_ts_response (_client, ctx)) {
++ GST_ERROR_OBJECT (client, "Failed to handle transport switch response");
++ goto error;
++ }
++ }
++ /* 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;
++ /* Checks whether server is 'coupling mode' or not */
++ GST_DEBUG_OBJECT (client, "server coupling mode [%d]",priv->coupling_mode );
++ if (priv->coupling_mode) {
++ gst_rtsp_wfd_client_trigger_request (_client, WFD_TRIGGER_TEARDOWN_COUPLING);
++ } else {
++ 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);
++ }
++ }
++
++ if (msg != NULL)
++ gst_wfd_message_free(msg);
++
++ return;
++
++error:
++
++ if (msg != NULL)
++ gst_wfd_message_free(msg);
++
++ return;
++}
++
++static gboolean
++handle_wfd_options_request (GstRTSPClient * client, GstRTSPContext * ctx, GstRTSPVersion version)
++{
++ 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;
++
++ if (version < GST_RTSP_VERSION_2_0) {
++ options |= GST_RTSP_RECORD;
++ options |= GST_RTSP_ANNOUNCE;
++ }
++
++ 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);
++ g_free (tmp);
++
++ 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 {
++ GST_INFO_OBJECT (_client, "user_agent is NULL and user_agent is optional.");
++ }
++
++ 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);
++ else {
++ send_generic_wfd_response (_client, GST_RTSP_STS_OK, ctx);
++ g_signal_emit (client,
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_SET_PARAM_MSG], 0, data);
++ }
++ } 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_DS_REQ_MSG,
++ M4_RES_MSG,
++ M5_REQ_MSG,
++ TEARDOWN_TRIGGER,
++ TEARDOWN_COUPLING_TRIGGER,
++ PLAY_TRIGGER,
++ PAUSE_TRIGGER,
++ TS_REQ_MSG,
++ TS_REP_REQ_MSG,
++} 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_video_codec (GstRTSPWFDClient * client, guint video_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 video 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 vidoe 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 vidoe codec: no factory...");
++ ret = FALSE;
++ goto no_factory;
++ }
++
++ gst_rtsp_media_factory_wfd_set_video_codec (factory, video_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);
++
++ 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;
++ }
++
++ /* set wfd2_audio_codecs & wfd2_video_formats if it is supported */
++ if (priv->wfd2_supported == 1) {
++ /* set the supported audio formats by the WFD server for direct streaming */
++ wfd_res =
++ gst_wfd_message_set_supported_wfd2_audio_codec (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 for direct streaming on wfd message...");
++ goto error;
++ }
++
++ /* set the supported Video formats by the WFD server for direct streaming */
++ wfd_res =
++ gst_wfd_message_set_supported_direct_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 for direct streaming 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_preferred_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;
++ }
++
++ /* set the preffered TCP ports for the WFD server */
++ wfd_res =
++ gst_wfd_messge_set_preferred_tcp_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 tcp ports parameter on wfd message...");
++ goto error;
++ }
++
++ /* set the buffer length for the WFD server */
++ wfd_res =
++ gst_wfd_message_set_buffer_length (msg, 0);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set tcp ports parameter on wfd message...");
++ goto error;
++ }
++
++ /* set the coupled sink for the WFD server */
++ wfd_res =
++ gst_wfd_message_set_coupled_sink (msg, GST_WFD_SINK_NOT_COUPLED, NULL, TRUE);
++
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set coupled sink parameter 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 {
++ gchar *append_data = NULL;
++
++ g_signal_emit (client, gst_rtsp_client_wfd_signals[SIGNAL_WFD_M3_REQ_MSG],
++ 0, *data, &append_data);
++
++ if (append_data) {
++ g_free (*data);
++ *data = append_data;
++ }
++
++ *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 */
++ GstWFDVideoCodecs tvideocodec = GST_WFD_VIDEO_UNKNOWN;
++ guint64 tcCEAResolution = GST_WFD_CEA_UNKNOWN;
++ guint64 tcVESAResolution = GST_WFD_VESA_UNKNOWN;
++ guint64 tcHHResolution = GST_WFD_HH_UNKNOWN;
++ GstWFDVideoH264Profile tcProfile = GST_WFD_H264_UNKNOWN_PROFILE;
++ GstWFDVideoH264Level tcLevel = GST_WFD_H264_LEVEL_UNKNOWN;
++ 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;
++ }
++
++ buf = g_string_new ("");
++ if (buf == NULL)
++ 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");
++ 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;
++ }
++
++ if (priv->caCodec != GST_WFD_AUDIO_UNKNOWN) {
++ taudiocodec = wfd_get_preferred_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->caCodec != GST_WFD_AUDIO_UNKNOWN) {
++ 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;
++ }
++
++ if(priv->wfd2_mode)
++ wfd_res =
++ gst_wfd_message_set_preferred_wfd2_audio_codec (msg, taudiocodec, taudiofreq,
++ taudiochannels, priv->cBitwidth, priv->caLatency);
++ else
++ wfd_res =
++ gst_wfd_message_set_preferred_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 */
++ tvideocodec = wfd_get_preferred_video_codec (priv->video_codec, priv->cvCodec);
++ GST_INFO_OBJECT (priv, "Set the video formats. source codec %d, sink codec %d, Negotiated code %d",
++ priv->video_codec, priv->cvCodec, tvideocodec);
++ priv->cvCodec = tvideocodec;
++
++ if (priv->cvCodec != GST_WFD_VIDEO_UNKNOWN) {
++ 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_preferred_resolution (resolution_supported,
++ priv->cCEAResolution, priv->video_native_resolution, &priv->cMaxWidth,
++ &priv->cMaxHeight, &priv->cFramerate, &priv->cInterleaved);
++ GST_DEBUG
++ ("wfd negotiated resolution: %" G_GUINT64_FORMAT ", 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_preferred_resolution (resolution_supported,
++ priv->cVESAResolution, priv->video_native_resolution,
++ &priv->cMaxWidth, &priv->cMaxHeight, &priv->cFramerate,
++ &priv->cInterleaved);
++ GST_DEBUG
++ ("wfd negotiated resolution: %" G_GUINT64_FORMAT ", 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_preferred_resolution (resolution_supported,
++ priv->cHHResolution, priv->video_native_resolution, &priv->cMaxWidth,
++ &priv->cMaxHeight, &priv->cFramerate, &priv->cInterleaved);
++ GST_DEBUG
++ ("wfd negotiated resolution: %" G_GUINT64_FORMAT ", 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...");
++ }
++ }
++
++ if (!_set_negotiated_video_codec (client, priv->cvCodec)) {
++ GST_ERROR_OBJECT (client, "Failed to set negotiated "
++ "video format to media factory...");
++ }
++
++ wfd_res =
++ gst_wfd_message_set_preferred_video_format (msg, priv->cvCodec,
++ priv->video_native_resolution, GST_WFD_CEA_UNKNOWN, tcCEAResolution,
++ tcVESAResolution, tcHHResolution, tcProfile, tcLevel, 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_ERROR_OBJECT (client, "Failed to set preffered video formats...");
++ goto error;
++ }
++
++ if (priv->direct_streaming_supported) {
++ wfd_res =
++ gst_wfd_message_set_preferred_direct_video_format (msg, priv->cvCodec,
++ priv->video_native_resolution, GST_WFD_CEA_UNKNOWN, tcCEAResolution,
++ tcVESAResolution, tcHHResolution, tcProfile, tcLevel, 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_ERROR_OBJECT (client, "Failed to set preffered video formats for direct streaming...");
++ goto error;
++ }
++ }
++
++ /* set the preffered RTP ports for the WFD server */
++ wfd_res =
++ gst_wfd_messge_set_preferred_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 == M4_DS_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 */
++ GstWFDAudioFreq taudiofreq = GST_WFD_FREQ_UNKNOWN;
++ GstWFDAudioChannels taudiochannels = GST_WFD_CHANNEL_UNKNOWN;
++
++ /* Parameters for the preffered video formats */
++ guint64 tcCEAResolution = GST_WFD_CEA_UNKNOWN;
++ guint64 tcVESAResolution = GST_WFD_VESA_UNKNOWN;
++ guint64 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;
++ }
++
++ /* create M4 for direct streaming 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;
++ }
++
++ buf = g_string_new ("");
++ if (buf == NULL)
++ 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;
++ }
++
++ 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_preferred_wfd2_audio_codec (msg,
++ priv->direct_detected_audio_codec, taudiofreq,
++ taudiochannels, priv->cBitwidth, priv->caLatency);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (priv, "Failed to set preffered audio formats for direct streaming...");
++ goto error;
++ }
++
++ /* Set the preffered video formats */
++ 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_preferred_resolution (resolution_supported,
++ priv->cCEAResolution, priv->video_native_resolution, &priv->cMaxWidth,
++ &priv->cMaxHeight, &priv->cFramerate, &priv->cInterleaved);
++ GST_DEBUG
++ ("wfd negotiated resolution: %" G_GUINT64_FORMAT ", 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_preferred_resolution (resolution_supported,
++ priv->cVESAResolution, priv->video_native_resolution,
++ &priv->cMaxWidth, &priv->cMaxHeight, &priv->cFramerate,
++ &priv->cInterleaved);
++ GST_DEBUG
++ ("wfd negotiated resolution: %" G_GUINT64_FORMAT ", 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_preferred_resolution (resolution_supported,
++ priv->cHHResolution, priv->video_native_resolution, &priv->cMaxWidth,
++ &priv->cMaxHeight, &priv->cFramerate, &priv->cInterleaved);
++ GST_DEBUG
++ ("wfd negotiated resolution: %" G_GUINT64_FORMAT ", width: %d, height: %d, framerate: %d, interleaved: %d",
++ tcHHResolution, priv->cMaxWidth, priv->cMaxHeight, priv->cFramerate,
++ priv->cInterleaved);
++ }
++
++ wfd_res =
++ gst_wfd_message_set_preferred_direct_video_format (msg,
++ priv->direct_detected_video_codec,
++ priv->video_native_resolution, GST_WFD_CEA_UNKNOWN, tcCEAResolution,
++ tcVESAResolution, tcHHResolution, tcProfile, tcLevel, 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_ERROR_OBJECT (client, "Failed to set preffered video formats for direct streaming...");
++ goto error;
++ }
++
++ wfd_res =
++ gst_wfd_message_set_direct_streaming_mode (msg, TRUE);
++
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client, "Failed to set preffered video formats for direct streaming...");
++ 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 == TS_REQ_MSG) {
++ /* create transport switch 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 preffered TCP ports for the WFD server */
++ if (priv->ts_mode == WFD_TS_UDP) {
++ wfd_res =
++ gst_wfd_messge_set_preferred_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 preferred RTP ports on wfd message...");
++ goto error;
++ }
++ } else {
++ wfd_res =
++ gst_wfd_messge_set_preferred_tcp_ports (msg, GST_WFD_RTSP_TRANS_RTP,
++ GST_WFD_RTSP_PROFILE_AVP, GST_WFD_RTSP_LOWER_TRANS_TCP, priv->crtp_port0_tcp, priv->crtp_port1_tcp);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set preferred TCP ports on wfd message...");
++ goto error;
++ }
++ }
++
++ wfd_res =
++ gst_wfd_message_set_buffer_length (msg, 200);
++ if (wfd_res != GST_WFD_OK) {
++ GST_ERROR_OBJECT (client,
++ "Failed to set preferred buffer length 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 {
++ gchar *append_data = NULL;
++
++ g_signal_emit (client,
++ gst_rtsp_client_wfd_signals[SIGNAL_WFD_M4_REQ_MSG], 0, *data,
++ &append_data);
++
++ if (append_data) {
++ g_free (*data);
++ *data = append_data;
++ }
++ *len = strlen (*data);
++ }
++ } else if (msg_type == M5_REQ_MSG) {
++ buf = g_string_new ("wfd_trigger_method: SETUP\r\n");
++ if (buf == NULL)
++ goto error;
++ *len = buf->len;
++ *data = g_string_free (buf, FALSE);
++ } else if (msg_type == TEARDOWN_TRIGGER) {
++ buf = g_string_new ("wfd_trigger_method: TEARDOWN\r\n");
++ if (buf == NULL)
++ goto error;
++ *len = buf->len;
++ *data = g_string_free (buf, FALSE);
++ } else if (msg_type == TEARDOWN_COUPLING_TRIGGER) {
++ buf = g_string_new ("wfd_trigger_method: TEARDOWN_COUPLING\r\n");
++ if (buf == NULL)
++ goto error;
++ *len = buf->len;
++ *data = g_string_free (buf, FALSE);
++ } else if (msg_type == PLAY_TRIGGER) {
++ buf = g_string_new ("wfd_trigger_method: PLAY\r\n");
++ if (buf == NULL)
++ goto error;
++ *len = buf->len;
++ *data = g_string_free (buf, FALSE);
++ } else if (msg_type == PAUSE_TRIGGER) {
++ buf = g_string_new ("wfd_trigger_method: PAUSE\r\n");
++ if (buf == NULL)
++ goto error;
++ *len = buf->len;
++ *data = g_string_free (buf, FALSE);
++ }
++
++ if (msg != NULL)
++ gst_wfd_message_free(msg);
++
++ return;
++
++error:
++
++ if (msg != NULL)
++ gst_wfd_message_free(msg);
++
++ *data = NULL;
++ *len = 0;
++
++ return;
++}
++
++/**
++* gst_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
++gst_prepare_request (GstRTSPWFDClient * client, GstRTSPMessage * request,
++ GstRTSPMethod method, gchar * url)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ gchar *str = NULL;
++
++ 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;
++
++ /* 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);
++ GST_DEBUG ("M3 server side message body: %s", msg);
++
++ res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen);
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (client, "Failed to set body data to rtsp message...");
++ goto error;
++ }
++
++ g_free (msg);
++ break;
++ }
++
++ /* Prepare SET_PARAMETER request */
++ case GST_RTSP_SET_PARAMETER:{
++ gchar *msg = NULL;
++ guint msglen = 0;
++
++ /* 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);
++ GST_DEBUG ("M4 server side message body: %s", msg);
++
++ res = gst_rtsp_message_set_body (request, (guint8 *) msg, msglen);
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (client, "Failed to set body data 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_TEARDOWN_COUPLING:{
++ 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_COUPLING_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
++gst_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);
++ g_free (tmp);
++ 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 = 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;
++ }
++
++ GST_DEBUG_OBJECT (client, "Sending M1 request.. (OPTIONS request)");
++
++ gst_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 };
++
++ res = gst_prepare_request (client, &request, GST_RTSP_GET_PARAMETER,
++ (gchar *) "rtsp://localhost/wfd1.0");
++
++ 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)...");
++
++ gst_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 };
++
++ res = gst_prepare_request (client, &request, GST_RTSP_SET_PARAMETER,
++ (gchar *) "rtsp://localhost/wfd1.0");
++
++ 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)...");
++
++ gst_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 };
++
++ res = prepare_trigger_request (client, &request, type, (gchar *) "rtsp://localhost/wfd1.0");
++
++ 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);
++
++ gst_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_video_codec (GstRTSPWFDClient * client,
++ guint8 video_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->video_codec = video_codec;
++ GST_DEBUG ("Video codec : %d", video_codec);
++
++ 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;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_client_set_coupling_mode (GstRTSPWFDClient * client,
++ gboolean coupling_mode)
++{
++ 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->coupling_mode = coupling_mode;
++
++ 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 gst_prepare_request function.*/
++static GstRTSPResult
++handle_M16_message (GstRTSPWFDClient * client)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPMessage request = { 0 };
++
++ res = gst_rtsp_message_init_request (&request, GST_RTSP_GET_PARAMETER,
++ (gchar *) "rtsp://localhost/wfd1.0");
++
++ if (res < 0) {
++ GST_ERROR ("init request failed");
++ return FALSE;
++ }
++
++ gst_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;
++}
++
++gboolean
++gst_rtsp_wfd_client_get_coupling_mode (GstRTSPWFDClient * client)
++{
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ g_return_val_if_fail (priv != NULL, 0);
++
++ return priv->coupling_mode;
++}
++
++void
++gst_rtsp_wfd_client_set_audio_freq (GstRTSPWFDClient * client, guint freq)
++{
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ g_return_if_fail (priv != NULL);
++
++ 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_if_fail (priv != NULL);
++
++ 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_if_fail (priv != NULL);
++
++ 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_if_fail (priv != NULL);
++
++ 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_if_fail (priv != NULL);
++
++ 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);
++ g_return_if_fail (priv != NULL);
++
++ 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_vid_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;
++}
++
++void
++gst_rtsp_wfd_client_set_wfd2_supported (GstRTSPWFDClient *client,
++ gint flag)
++{
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ g_return_if_fail (priv != NULL);
++
++ priv->wfd2_supported = flag;
++}
++
++static void
++direct_stream_end_cb (GstRTSPMediaFactoryWFD *factory, void *user_data)
++{
++ GstRTSPWFDClient *client = GST_RTSP_WFD_CLIENT_CAST (user_data);
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ GstRTSPResult res = GST_RTSP_OK;
++
++ priv->direct_streaming_state = 0;
++ res = handle_M4_message (client);
++
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (client, "Failed to send message for direct streaming");
++ }
++}
++
++GstRTSPResult
++gst_rtsp_wfd_client_set_direct_streaming(GstRTSPWFDClient * client,
++ gint direct_streaming, gchar *urisrc)
++{
++ GstRTSPClient *parent_client = GST_RTSP_CLIENT_CAST (client);
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ GstRTSPResult res = GST_RTSP_OK;
++
++ GstRTSPMediaFactory *factory = NULL;
++ GstRTSPMountPoints *mount_points = NULL;
++ gchar *path = NULL;
++ gint matched = 0;
++
++ if (priv->direct_streaming_supported == FALSE) {
++ GST_ERROR_OBJECT (client, "Direct streaming not supported by client");
++ return GST_RTSP_ERROR;
++ }
++
++ if (priv->direct_streaming_state == direct_streaming) {
++ GST_DEBUG_OBJECT (client, "Direct streaming state not changed");
++ return res;
++ }
++
++ if (!(mount_points = gst_rtsp_client_get_mount_points (parent_client))) {
++ res = GST_RTSP_ERROR;
++ GST_ERROR_OBJECT (client, "Failed to set direct streaing: no mount points...");
++ goto no_mount_points;
++ }
++
++ path = g_strdup(WFD_MOUNT_POINT);
++ if (!path) {
++ res = GST_RTSP_ERROR;
++ GST_ERROR_OBJECT (client, "Failed to set direct streaing: no path...");
++ goto no_path;
++ }
++
++ if (!(factory = gst_rtsp_mount_points_match (mount_points,
++ path, &matched))) {
++ GST_ERROR_OBJECT (client, "Failed to set direct streaing: no factory...");
++ res = GST_RTSP_ERROR;
++ goto no_factory;
++ }
++
++ if (direct_streaming) {
++ res = gst_rtsp_media_factory_wfd_uri_type_find (factory,
++ urisrc, &priv->direct_detected_video_codec,
++ &priv->direct_detected_audio_codec);
++
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (client, "Failed to create direct streaming pipeline");
++ goto no_pipe;
++ }
++ }
++
++ if (!(priv->direct_detected_video_codec & GST_WFD_VIDEO_H264)) {
++ GST_ERROR_OBJECT (client, "Detected video codec not supported");
++ res = GST_RTSP_ERROR;
++ goto no_pipe;
++ }
++
++ if (!(priv->direct_detected_audio_codec & GST_WFD_AUDIO_AAC ||
++ priv->direct_detected_audio_codec & GST_WFD_AUDIO_LPCM ||
++ priv->direct_detected_audio_codec & GST_WFD_AUDIO_AC3)) {
++ GST_ERROR_OBJECT (client, "Detected audio codec not supported");
++ res = GST_RTSP_ERROR;
++ goto no_pipe;
++ }
++
++ g_signal_connect_object (GST_RTSP_MEDIA_FACTORY_WFD_CAST (factory), "direct-stream-end",
++ G_CALLBACK (direct_stream_end_cb), client, 0);
++
++ res = gst_rtsp_media_factory_wfd_set_direct_streaming (factory,
++ direct_streaming, urisrc);
++
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (client, "Failed to create direct streaming pipeline");
++ goto no_pipe;
++ }
++
++ if (direct_streaming) {
++ res = handle_M4_direct_streaming_message (client);
++
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (client, "Failed to send message for direct streaming");
++ goto no_pipe;
++ }
++ }
++
++ priv->direct_streaming_state = direct_streaming;
++
++no_pipe:
++ g_object_unref(factory);
++no_factory:
++ g_free(path);
++no_path:
++ g_object_unref(mount_points);
++no_mount_points:
++ return res;
++}
++
++/**
++* prepare_direct_streaming_request:
++* @client: client object
++* @request : requst message to be prepared
++* @url : url need to be in the request
++*
++* Prepares request based on @method & @message_type
++*
++* Returns: a #GstRTSPResult.
++*/
++static GstRTSPResult
++prepare_direct_streaming_request (GstRTSPWFDClient * client, GstRTSPMessage * request)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ gchar *msg = NULL;
++ guint msglen = 0;
++ GString *msglength;
++
++ GST_DEBUG_OBJECT (client, "Preparing request for direct streaming");
++
++ /* initialize the request */
++ res = gst_rtsp_message_init_request (request, GST_RTSP_SET_PARAMETER,
++ (gchar *) "rtsp://localhost/wfd1.0");
++ if (res < 0) {
++ GST_ERROR ("init request failed");
++ return res;
++ }
++
++ /* 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_DS_REQ_MSG, &msg, &msglen);
++ msglength = g_string_new ("");
++ g_string_append_printf (msglength, "%d", msglen);
++ GST_DEBUG ("M4 for direct streaming 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);
++
++ return res;
++
++error:
++ return GST_RTSP_ERROR;
++}
++
++static GstRTSPResult
++handle_M4_direct_streaming_message (GstRTSPWFDClient * client)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPMessage request = { 0 };
++
++ res = prepare_direct_streaming_request (client, &request);
++ 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 for direct streaming (M4)...");
++
++ gst_send_request (client, NULL, &request);
++
++ return res;
++
++error:
++ return res;
++}
++
++/**
++* prepare_transport_switch_request:
++* @client: client object
++* @request : requst message to be prepared
++* @url : url need to be in the request
++*
++* Prepares request based on @method & @message_type
++*
++* Returns: a #GstRTSPResult.
++*/
++static GstRTSPResult
++prepare_transport_switch_request (GstRTSPWFDClient * client, GstRTSPMessage * request)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ gchar *url = NULL;
++ gchar *msg = NULL;
++ guint msglen = 0;
++ GString *msglength;
++
++ GstRTSPMethod method = GST_RTSP_SET_PARAMETER;
++
++ url = g_strdup ("rtsp://localhost/wfd1.0");
++ if (!url)
++ return GST_RTSP_ERROR;
++
++ GST_DEBUG_OBJECT (client, "Preparing request for transport switch");
++
++ /* initialize the request */
++ res = gst_rtsp_message_init_request (request, method, url);
++ g_free (url);
++ if (res < 0) {
++ GST_ERROR ("init request failed");
++ return res;
++ }
++
++ /* 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, TS_REQ_MSG, &msg, &msglen);
++ msglength = g_string_new ("");
++ g_string_append_printf (msglength, "%d", msglen);
++ GST_DEBUG ("Transport switch 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);
++
++ return res;
++
++error:
++ return GST_RTSP_ERROR;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_client_switch_to_udp (GstRTSPWFDClient * client)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPMessage request = { 0 };
++ GList *tl = NULL;
++ GPtrArray *ta = NULL;
++
++ if (client->priv->ts_mode == WFD_TS_UDP) {
++ GST_ERROR_OBJECT (client, "Transport already UDP");
++ return res;
++ }
++
++ ta = g_ptr_array_new();
++
++ tl = gst_rtsp_stream_transport_filter (client->priv->stats.stream, NULL, NULL);
++ client->priv->transports = tl;
++ g_ptr_array_add (ta, tl->data);
++
++ client->priv->ts_mode = WFD_TS_UDP;
++ res = prepare_transport_switch_request (client, &request);
++ if (GST_RTSP_OK != res) {
++ GST_ERROR_OBJECT (client, "Failed to prepare transport switch request....\n");
++ goto error;
++ }
++
++ GST_DEBUG_OBJECT (client, "Sending SET_PARAMETER request message for transport switch...");
++
++ gst_send_request (client, NULL, &request);
++
++ gst_rtsp_media_set_state (client->priv->media, GST_STATE_PAUSED, ta);
++
++ g_ptr_array_free (ta, FALSE);
++
++ return res;
++
++error:
++ g_ptr_array_free (ta, FALSE);
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_client_switch_to_tcp (GstRTSPWFDClient * client)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPMessage request = { 0 };
++ GList *tl = NULL;
++ GPtrArray *ta = NULL;
++
++ ta = g_ptr_array_new();
++
++ tl = gst_rtsp_stream_transport_filter (client->priv->stats.stream, NULL, NULL);
++ client->priv->transports = tl;
++ g_ptr_array_add (ta, tl->data);
++
++ if (client->priv->ts_mode == WFD_TS_TCP) {
++ GST_ERROR_OBJECT (client, "Transport already TCP");
++ return res;
++ }
++
++ client->priv->ts_mode = WFD_TS_TCP;
++ res = prepare_transport_switch_request (client, &request);
++ if (GST_RTSP_OK != res) {
++ GST_ERROR_OBJECT (client, "Failed to prepare transport switch request....\n");
++ goto error;
++ }
++
++ GST_DEBUG_OBJECT (client, "Sending SET_PARAMETER request message for transport switch...");
++
++ gst_send_request (client, NULL, &request);
++
++ gst_rtsp_media_set_state (client->priv->media, GST_STATE_PAUSED, ta);
++
++ g_ptr_array_free (ta, FALSE);
++
++ return res;
++
++error:
++ g_ptr_array_free (ta, FALSE);
++ return res;
++}
++gchar * gst_rtsp_wfd_client_get_sink_user_agent (GstRTSPWFDClient * client)
++{
++ char *str = NULL;
++ GstRTSPWFDClientPrivate *priv = GST_RTSP_WFD_CLIENT_GET_PRIVATE (client);
++ g_return_val_if_fail (priv != NULL, NULL);
++
++ if (priv->sink_user_agent != NULL)
++ str = g_strdup (priv->sink_user_agent);
++
++ return str;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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 <gst/gst.h>
++#include <gst/rtsp/gstrtspconnection.h>
++
++#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_TEARDOWN_COUPLING,
++ 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;
++
++#if 0 /* unused variable */
++ gint supported_methods;
++#endif
++ /*< 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);
++ gboolean (*configure_client_media) (GstRTSPClient * client,
++ GstRTSPMedia * media, GstRTSPStream * stream,
++ 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);
++ void (*wfd_playing_done) (GstRTSPWFDClient *client);
++ void (*wfd_rtp_stats) (GstRTSPWFDClient *client, GstStructure *stats);
++ gchar* (*wfd_handle_m3_req_msg) (GstRTSPWFDClient *client, gchar *data);
++ void (*wfd_handle_m3_res_msg) (GstRTSPWFDClient *client, gchar *data);
++ gchar* (*wfd_handle_m4_req_msg) (GstRTSPWFDClient *client, gchar *data);
++ void (*wfd_handle_set_param_msg) (GstRTSPWFDClient *client, gchar *data);
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING_LARGE];
++};
++
++GST_RTSP_SERVER_API
++GType gst_rtsp_wfd_client_get_type (void);
++
++GST_RTSP_SERVER_API
++GstRTSPWFDClient * gst_rtsp_wfd_client_new (void);
++
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_host_address (
++ GstRTSPWFDClient *client, const gchar * address);
++
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_start_wfd(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_trigger_request (
++ GstRTSPWFDClient * client, GstWFDTriggerType type);
++
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_set_video_supported_resolution (
++ GstRTSPWFDClient * client, guint64 supported_reso);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_set_video_native_resolution (
++ GstRTSPWFDClient * client, guint64 native_reso);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_set_video_codec (
++ GstRTSPWFDClient * client, guint8 video_codec);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_set_audio_codec (
++ GstRTSPWFDClient * client, guint8 audio_codec);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_set_direct_streaming(
++ GstRTSPWFDClient * client, gint direct_streaming, gchar *urisrc);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_set_coupling_mode(
++ GstRTSPWFDClient * client, gboolean coupling_mode);
++
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_prepare_request (GstRTSPWFDClient * client,
++ GstRTSPMessage * request, GstRTSPMethod method, gchar * url);
++
++GST_RTSP_SERVER_API
++void gst_send_request (GstRTSPWFDClient * client,
++ GstRTSPSession * session, GstRTSPMessage * request);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_switch_to_udp (GstRTSPWFDClient * client);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_client_switch_to_tcp (GstRTSPWFDClient * client);
++
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_audio_codec(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_audio_freq(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_audio_channels(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_audio_bit_width(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_audio_latency(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_video_codec(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_video_native(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint64 gst_rtsp_wfd_client_get_video_native_resolution(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint64 gst_rtsp_wfd_client_get_video_cea_resolution(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint64 gst_rtsp_wfd_client_get_video_vesa_resolution(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint64 gst_rtsp_wfd_client_get_video_hh_resolution(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_video_profile(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_video_level(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_video_latency(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_video_max_height(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_video_max_width(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_video_framerate(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_video_min_slice_size(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_video_slice_enc_params(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_wfd_client_get_video_framerate_control(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_rtp_port0(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_rtp_port1(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++gboolean gst_rtsp_wfd_client_get_edid_supported(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_edid_hresolution(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++guint32 gst_rtsp_wfd_client_get_edid_vresolution(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++gboolean gst_rtsp_wfd_client_get_protection_enabled(GstRTSPWFDClient *client);
++
++GST_RTSP_SERVER_API
++gboolean gst_rtsp_wfd_client_get_coupling_mode(GstRTSPWFDClient *client);
++
++
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_audio_freq(GstRTSPWFDClient *client, guint freq);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_edid_supported(GstRTSPWFDClient *client, gboolean supported);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_edid_hresolution(GstRTSPWFDClient *client, guint32 reso);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_edid_vresolution(GstRTSPWFDClient *client, guint32 reso);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_protection_enabled(GstRTSPWFDClient *client, gboolean enable);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_hdcp_version(GstRTSPWFDClient *client, GstWFDHDCPProtection version);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_hdcp_port(GstRTSPWFDClient *client, guint32 port);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_keep_alive_flag(GstRTSPWFDClient *client, gboolean flag);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_aud_codec(GstRTSPWFDClient *client, guint acodec);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_audio_channels(GstRTSPWFDClient *client, guint channels);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_audio_bit_width(GstRTSPWFDClient *client, guint bwidth);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_audio_latency(GstRTSPWFDClient *client, guint latency);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_vid_codec(GstRTSPWFDClient *client, guint vcodec);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_native(GstRTSPWFDClient *client, guint native);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_vid_native_resolution(GstRTSPWFDClient *client, guint64 res);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_cea_resolution(GstRTSPWFDClient *client, guint64 res);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_vesa_resolution(GstRTSPWFDClient *client, guint64 res);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_hh_resolution(GstRTSPWFDClient *client, guint64 res);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_profile(GstRTSPWFDClient *client, guint profile);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_level(GstRTSPWFDClient *client, guint level);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_latency(GstRTSPWFDClient *client, guint latency);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_max_height(GstRTSPWFDClient *client, guint32 height);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_max_width(GstRTSPWFDClient *client, guint32 width);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_framerate(GstRTSPWFDClient *client, guint32 framerate);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_min_slice_size(GstRTSPWFDClient *client, guint32 slice_size);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_slice_enc_params(GstRTSPWFDClient *client, guint32 enc_params);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_video_framerate_control(GstRTSPWFDClient *client, guint framerate);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_rtp_port0(GstRTSPWFDClient *client, guint32 port);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_rtp_port1(GstRTSPWFDClient *client, guint32 port);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_wfd_client_set_wfd2_supported (GstRTSPWFDClient *client, gint flag);
++
++GST_RTSP_SERVER_API
++gchar *gst_rtsp_wfd_client_get_sink_user_agent(GstRTSPWFDClient *client);
++
++/**
++ * 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__ */
--- /dev/null
-handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
+ *
+ * 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 #GstRTSPClient 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_client_set_connection() before it can be attached to a #GMainContext
+ * using gst_rtsp_client_attach(). From then on the client will handle requests
+ * on the connection.
+ *
+ * Use gst_rtsp_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)
+ */
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+
+ #include <stdio.h>
+ #include <string.h>
+
+ #include <gst/sdp/gstmikey.h>
+ #include <gst/rtsp/gstrtsp-enumtypes.h>
+
+ #include "rtsp-client.h"
+ #include "rtsp-sdp.h"
+ #include "rtsp-params.h"
+ #include "rtsp-server-internal.h"
+
+ typedef enum
+ {
+ TUNNEL_STATE_UNKNOWN,
+ TUNNEL_STATE_GET,
+ TUNNEL_STATE_POST
+ } GstRTSPTunnelState;
+
+ /* locking order:
+ * send_lock, lock, tunnels_lock
+ */
+
+ struct _GstRTSPClientPrivate
+ {
+ GMutex lock; /* protects everything else */
+ GMutex send_lock;
+ GMutex watch_lock;
+ GstRTSPConnection *connection;
+ GstRTSPWatch *watch;
+ GMainContext *watch_context;
+ gchar *server_ip;
+ gboolean is_ipv6;
+
+ /* protected by send_lock */
+ GstRTSPClientSendFunc send_func;
+ gpointer send_data;
+ GDestroyNotify send_notify;
+ GstRTSPClientSendMessagesFunc send_messages_func;
+ gpointer send_messages_data;
+ GDestroyNotify send_messages_notify;
+ GArray *data_seqs;
+
+ GstRTSPSessionPool *session_pool;
+ gulong session_removed_id;
+ GstRTSPMountPoints *mount_points;
+ GstRTSPAuth *auth;
+ GstRTSPThreadPool *thread_pool;
+
+ /* 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;
+
+ GHashTable *transports;
+ GList *sessions;
+ guint sessions_cookie;
+
+ gboolean drop_backlog;
+ gint post_session_timeout;
+
+ guint content_length_limit;
+
+ gboolean had_session;
+ GSource *rtsp_ctrl_timeout;
+ guint rtsp_ctrl_timeout_cnt;
+
+ /* The version currently being used */
+ GstRTSPVersion version;
+
+ GHashTable *pipelined_requests; /* pipelined_request_id -> session_id */
+ GstRTSPTunnelState tstate;
+ };
+
+ typedef struct
+ {
+ guint8 channel;
+ guint seq;
+ } DataSeq;
+
+ static GMutex tunnels_lock;
+ static GHashTable *tunnels; /* protected by tunnels_lock */
+
+ #define WATCH_BACKLOG_SIZE 100
+
+ #define DEFAULT_SESSION_POOL NULL
+ #define DEFAULT_MOUNT_POINTS NULL
+ #define DEFAULT_DROP_BACKLOG TRUE
+ #define DEFAULT_POST_SESSION_TIMEOUT -1
+
+ #define RTSP_CTRL_CB_INTERVAL 1
+ #define RTSP_CTRL_TIMEOUT_VALUE 60
+
+ enum
+ {
+ PROP_0,
+ PROP_SESSION_POOL,
+ PROP_MOUNT_POINTS,
+ PROP_DROP_BACKLOG,
+ PROP_POST_SESSION_TIMEOUT,
+ PROP_LAST
+ };
+
+ enum
+ {
+ SIGNAL_CLOSED,
+ SIGNAL_NEW_SESSION,
+ SIGNAL_PRE_OPTIONS_REQUEST,
+ SIGNAL_OPTIONS_REQUEST,
+ SIGNAL_PRE_DESCRIBE_REQUEST,
+ SIGNAL_DESCRIBE_REQUEST,
+ SIGNAL_PRE_SETUP_REQUEST,
+ SIGNAL_SETUP_REQUEST,
+ SIGNAL_PRE_PLAY_REQUEST,
+ SIGNAL_PLAY_REQUEST,
+ SIGNAL_PRE_PAUSE_REQUEST,
+ SIGNAL_PAUSE_REQUEST,
+ SIGNAL_PRE_TEARDOWN_REQUEST,
+ SIGNAL_TEARDOWN_REQUEST,
+ SIGNAL_PRE_SET_PARAMETER_REQUEST,
+ SIGNAL_SET_PARAMETER_REQUEST,
+ SIGNAL_PRE_GET_PARAMETER_REQUEST,
+ SIGNAL_GET_PARAMETER_REQUEST,
+ SIGNAL_HANDLE_RESPONSE,
+ SIGNAL_SEND_MESSAGE,
+ SIGNAL_PRE_ANNOUNCE_REQUEST,
+ SIGNAL_ANNOUNCE_REQUEST,
+ SIGNAL_PRE_RECORD_REQUEST,
+ SIGNAL_RECORD_REQUEST,
+ SIGNAL_CHECK_REQUIREMENTS,
+ SIGNAL_LAST
+ };
+
+ GST_DEBUG_CATEGORY_STATIC (rtsp_client_debug);
+ #define GST_CAT_DEFAULT rtsp_client_debug
+
+ static guint gst_rtsp_client_signals[SIGNAL_LAST] = { 0 };
+
+ static void gst_rtsp_client_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_client_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_client_finalize (GObject * obj);
+
+ static void rtsp_ctrl_timeout_remove (GstRTSPClient * client);
+
+ static GstSDPMessage *create_sdp (GstRTSPClient * client, GstRTSPMedia * media);
+ static gboolean handle_sdp (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPMedia * media, GstSDPMessage * sdp);
+ static gboolean default_configure_client_media (GstRTSPClient * client,
+ GstRTSPMedia * media, GstRTSPStream * stream, GstRTSPContext * ctx);
+ static gboolean default_configure_client_transport (GstRTSPClient * client,
+ GstRTSPContext * ctx, GstRTSPTransport * ct);
+ static GstRTSPResult default_params_set (GstRTSPClient * client,
+ GstRTSPContext * ctx);
+ 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, GstRTSPVersion version);
++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);
+ static GstRTSPStatusCode default_pre_signal_handler (GstRTSPClient * client,
+ GstRTSPContext * ctx);
+ static gboolean pre_signal_accumulator (GSignalInvocationHint * ihint,
+ GValue * return_accu, const GValue * handler_return, gpointer data);
++gboolean gst_rtsp_media_has_completed_sender (GstRTSPMedia * media);
+
+ G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPClient, gst_rtsp_client, G_TYPE_OBJECT);
+
+ static void
+ gst_rtsp_client_class_init (GstRTSPClientClass * klass)
+ {
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gst_rtsp_client_get_property;
+ gobject_class->set_property = gst_rtsp_client_set_property;
+ gobject_class->finalize = gst_rtsp_client_finalize;
+
+ klass->create_sdp = create_sdp;
+ klass->handle_sdp = handle_sdp;
+ klass->configure_client_media = default_configure_client_media;
+ klass->configure_client_transport = default_configure_client_transport;
+ 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;
++ klass->handle_play_request = default_handle_play_request;
+
+ klass->pre_options_request = default_pre_signal_handler;
+ klass->pre_describe_request = default_pre_signal_handler;
+ klass->pre_setup_request = default_pre_signal_handler;
+ klass->pre_play_request = default_pre_signal_handler;
+ klass->pre_pause_request = default_pre_signal_handler;
+ klass->pre_teardown_request = default_pre_signal_handler;
+ klass->pre_set_parameter_request = default_pre_signal_handler;
+ klass->pre_get_parameter_request = default_pre_signal_handler;
+ klass->pre_announce_request = default_pre_signal_handler;
+ klass->pre_record_request = default_pre_signal_handler;
+
+ g_object_class_install_property (gobject_class, PROP_SESSION_POOL,
+ g_param_spec_object ("session-pool", "Session Pool",
+ "The session pool to use for client session",
+ GST_TYPE_RTSP_SESSION_POOL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_MOUNT_POINTS,
+ g_param_spec_object ("mount-points", "Mount Points",
+ "The mount points to use for client session",
+ GST_TYPE_RTSP_MOUNT_POINTS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_DROP_BACKLOG,
+ g_param_spec_boolean ("drop-backlog", "Drop Backlog",
+ "Drop data when the backlog queue is full",
+ DEFAULT_DROP_BACKLOG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClient::post-session-timeout:
+ *
+ * An extra tcp timeout ( > 0) after session timeout, in seconds.
+ * The tcp connection will be kept alive until this timeout happens to give
+ * the client a possibility to reuse the connection.
+ * 0 means that the connection will be closed immediately after the session
+ * timeout.
+ *
+ * Default value is -1 seconds, meaning that we let the system close
+ * the connection.
+ *
+ * Since: 1.18
+ */
+ g_object_class_install_property (gobject_class, PROP_POST_SESSION_TIMEOUT,
+ g_param_spec_int ("post-session-timeout", "Post Session Timeout",
+ "An extra TCP connection timeout after session timeout", G_MININT,
+ G_MAXINT, DEFAULT_POST_SESSION_TIMEOUT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_rtsp_client_signals[SIGNAL_CLOSED] =
+ g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTSPClientClass, closed), NULL, NULL, NULL,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ gst_rtsp_client_signals[SIGNAL_NEW_SESSION] =
+ g_signal_new ("new-session", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTSPClientClass, new_session), NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_RTSP_SESSION);
+
+ /**
+ * GstRTSPClient::pre-options-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_OPTIONS_REQUEST] =
+ g_signal_new ("pre-options-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_options_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::options-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST] =
+ g_signal_new ("options-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, options_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-describe-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_DESCRIBE_REQUEST] =
+ g_signal_new ("pre-describe-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_describe_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::describe-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST] =
+ g_signal_new ("describe-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, describe_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-setup-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_SETUP_REQUEST] =
+ g_signal_new ("pre-setup-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_setup_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::setup-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST] =
+ g_signal_new ("setup-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, setup_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-play-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_PLAY_REQUEST] =
+ g_signal_new ("pre-play-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_play_request), pre_signal_accumulator, NULL,
+ NULL, GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::play-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST] =
+ g_signal_new ("play-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, play_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-pause-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_PAUSE_REQUEST] =
+ g_signal_new ("pre-pause-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_pause_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pause-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST] =
+ g_signal_new ("pause-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, pause_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-teardown-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_TEARDOWN_REQUEST] =
+ g_signal_new ("pre-teardown-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_teardown_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::teardown-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST] =
+ g_signal_new ("teardown-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, teardown_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-set-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_SET_PARAMETER_REQUEST] =
+ g_signal_new ("pre-set-parameter-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_set_parameter_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::set-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_SET_PARAMETER_REQUEST] =
+ g_signal_new ("set-parameter-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ set_parameter_request), NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-get-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_GET_PARAMETER_REQUEST] =
+ g_signal_new ("pre-get-parameter-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_get_parameter_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::get-parameter-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_GET_PARAMETER_REQUEST] =
+ g_signal_new ("get-parameter-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ get_parameter_request), NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::handle-response:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE] =
+ g_signal_new ("handle-response", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ handle_response), NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::send-message:
+ * @client: The RTSP client
+ * @session: (type GstRtspServer.RTSPSession): The session
+ * @message: (type GstRtsp.RTSPMessage): The message
+ */
+ gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE] =
+ g_signal_new ("send-message", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ send_message), NULL, NULL, NULL,
+ G_TYPE_NONE, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_POINTER);
+
+ /**
+ * GstRTSPClient::pre-announce-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_ANNOUNCE_REQUEST] =
+ g_signal_new ("pre-announce-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_announce_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::announce-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST] =
+ g_signal_new ("announce-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, announce_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::pre-record-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ *
+ * Returns: a #GstRTSPStatusCode, GST_RTSP_STS_OK in case of success,
+ * otherwise an appropriate return code
+ *
+ * Since: 1.12
+ */
+ gst_rtsp_client_signals[SIGNAL_PRE_RECORD_REQUEST] =
+ g_signal_new ("pre-record-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ pre_record_request), pre_signal_accumulator, NULL, NULL,
+ GST_TYPE_RTSP_STATUS_CODE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::record-request:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ */
+ gst_rtsp_client_signals[SIGNAL_RECORD_REQUEST] =
+ g_signal_new ("record-request", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass, record_request),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CONTEXT);
+
+ /**
+ * GstRTSPClient::check-requirements:
+ * @client: a #GstRTSPClient
+ * @ctx: (type GstRtspServer.RTSPContext): a #GstRTSPContext
+ * @arr: a NULL-terminated array of strings
+ *
+ * Returns: a newly allocated string with comma-separated list of
+ * unsupported options. An empty string must be returned if
+ * all options are supported.
+ *
+ * Since: 1.6
+ */
+ gst_rtsp_client_signals[SIGNAL_CHECK_REQUIREMENTS] =
+ g_signal_new ("check-requirements", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPClientClass,
+ check_requirements), NULL, NULL, NULL,
+ G_TYPE_STRING, 2, GST_TYPE_RTSP_CONTEXT, G_TYPE_STRV);
+
+ tunnels =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ g_mutex_init (&tunnels_lock);
+
+ GST_DEBUG_CATEGORY_INIT (rtsp_client_debug, "rtspclient", 0, "GstRTSPClient");
+ }
+
+ static void
+ gst_rtsp_client_init (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = gst_rtsp_client_get_instance_private (client);
+
+ client->priv = priv;
+
+ g_mutex_init (&priv->lock);
+ g_mutex_init (&priv->send_lock);
+ g_mutex_init (&priv->watch_lock);
+ priv->data_seqs = g_array_new (FALSE, FALSE, sizeof (DataSeq));
+ priv->drop_backlog = DEFAULT_DROP_BACKLOG;
+ priv->post_session_timeout = DEFAULT_POST_SESSION_TIMEOUT;
+ priv->transports =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+ g_object_unref);
+ priv->pipelined_requests = g_hash_table_new_full (g_str_hash,
+ g_str_equal, g_free, g_free);
+ priv->tstate = TUNNEL_STATE_UNKNOWN;
+ priv->content_length_limit = G_MAXUINT;
+ }
+
+ static GstRTSPFilterResult
+ filter_session_media (GstRTSPSession * sess, GstRTSPSessionMedia * sessmedia,
+ gpointer user_data)
+ {
+ gboolean *closed = user_data;
+ GstRTSPMedia *media;
+ guint i, n_streams;
+ gboolean is_all_udp = TRUE;
+
+ media = gst_rtsp_session_media_get_media (sessmedia);
+ n_streams = gst_rtsp_media_n_streams (media);
+
+ for (i = 0; i < n_streams; i++) {
+ GstRTSPStreamTransport *transport =
+ gst_rtsp_session_media_get_transport (sessmedia, i);
+ const GstRTSPTransport *rtsp_transport;
+
+ if (!transport)
+ continue;
+
+ rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);
+ if (rtsp_transport
+ && rtsp_transport->lower_transport != GST_RTSP_LOWER_TRANS_UDP
+ && rtsp_transport->lower_transport != GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+ is_all_udp = FALSE;
+ break;
+ }
+ }
+
+ if (!is_all_udp || gst_rtsp_media_is_stop_on_disconnect (media)) {
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);
+ return GST_RTSP_FILTER_REMOVE;
+ } else {
+ *closed = FALSE;
+ return GST_RTSP_FILTER_KEEP;
+ }
+ }
+
+ static void
+ client_watch_session (GstRTSPClient * client, GstRTSPSession * session)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+
+ g_mutex_lock (&priv->lock);
+ /* check if we already know about this session */
+ if (g_list_find (priv->sessions, session) == NULL) {
+ GST_INFO ("watching session %p", session);
+
+ priv->sessions = g_list_prepend (priv->sessions, g_object_ref (session));
+ priv->sessions_cookie++;
+
+ /* connect removed session handler, it will be disconnected when the last
+ * session gets removed */
+ if (priv->session_removed_id == 0)
+ priv->session_removed_id = g_signal_connect_data (priv->session_pool,
+ "session-removed", G_CALLBACK (client_session_removed),
+ g_object_ref (client), (GClosureNotify) g_object_unref, 0);
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return;
+ }
+
+ /* should be called with lock */
+ static void
+ client_unwatch_session (GstRTSPClient * client, GstRTSPSession * session,
+ GList * link)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+
+ GST_INFO ("client %p: unwatch session %p", client, session);
+
+ if (link == NULL) {
+ link = g_list_find (priv->sessions, session);
+ if (link == NULL)
+ return;
+ }
+
+ priv->sessions = g_list_delete_link (priv->sessions, link);
+ priv->sessions_cookie++;
+
+ /* if this was the last session, disconnect the handler.
+ * This will also drop the extra client ref */
+ if (!priv->sessions) {
+ g_signal_handler_disconnect (priv->session_pool, priv->session_removed_id);
+ priv->session_removed_id = 0;
+ }
+
+ if (!priv->drop_backlog) {
+ /* unlink all media managed in this session */
+ gst_rtsp_session_filter (session, filter_session_media, client);
+ }
+
+ /* remove the session */
+ g_object_unref (session);
+ }
+
+ static GstRTSPFilterResult
+ cleanup_session (GstRTSPClient * client, GstRTSPSession * sess,
+ gpointer user_data)
+ {
+ gboolean *closed = user_data;
+ GstRTSPClientPrivate *priv = client->priv;
+
+ if (priv->drop_backlog) {
+ /* unlink all media managed in this session. This needs to happen
+ * without the client lock, so we really want to do it here. */
+ gst_rtsp_session_filter (sess, filter_session_media, user_data);
+ }
+
+ if (*closed)
+ return GST_RTSP_FILTER_REMOVE;
+ else
+ return GST_RTSP_FILTER_KEEP;
+ }
+
+ static void
+ clean_cached_media (GstRTSPClient * client, gboolean unprepare)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+
+ if (priv->path) {
+ g_free (priv->path);
+ priv->path = NULL;
+ }
+ if (priv->media) {
+ if (unprepare)
+ gst_rtsp_media_unprepare (priv->media);
+ g_object_unref (priv->media);
+ priv->media = NULL;
+ }
+ }
+
+ /* A client is finalized when the connection is broken */
+ static void
+ gst_rtsp_client_finalize (GObject * obj)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (obj);
+ GstRTSPClientPrivate *priv = client->priv;
+
+ GST_INFO ("finalize client %p", client);
+
+ /* the watch and related state should be cleared before finalize
+ * as the watch actually holds a strong reference to the client */
+ g_assert (priv->watch == NULL);
+ g_assert (priv->rtsp_ctrl_timeout == NULL);
+
+ if (priv->watch_context) {
+ g_main_context_unref (priv->watch_context);
+ priv->watch_context = NULL;
+ }
+
+ gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
+ gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
+
+ /* all sessions should have been removed by now. We keep a ref to
+ * the client object for the session removed handler. The ref is
+ * dropped when the last session is removed from the list. */
+ g_assert (priv->sessions == NULL);
+ g_assert (priv->session_removed_id == 0);
+
+ g_array_unref (priv->data_seqs);
+ g_hash_table_unref (priv->transports);
+ g_hash_table_unref (priv->pipelined_requests);
+
+ if (priv->connection)
+ gst_rtsp_connection_free (priv->connection);
+ if (priv->session_pool) {
+ g_object_unref (priv->session_pool);
+ }
+ if (priv->mount_points)
+ g_object_unref (priv->mount_points);
+ if (priv->auth)
+ g_object_unref (priv->auth);
+ if (priv->thread_pool)
+ g_object_unref (priv->thread_pool);
+
+ clean_cached_media (client, TRUE);
+
+ g_free (priv->server_ip);
+ g_mutex_clear (&priv->lock);
+ g_mutex_clear (&priv->send_lock);
+ g_mutex_clear (&priv->watch_lock);
+
+ G_OBJECT_CLASS (gst_rtsp_client_parent_class)->finalize (obj);
+ }
+
+ static void
+ gst_rtsp_client_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (object);
+ GstRTSPClientPrivate *priv = client->priv;
+
+ switch (propid) {
+ case PROP_SESSION_POOL:
+ g_value_take_object (value, gst_rtsp_client_get_session_pool (client));
+ break;
+ case PROP_MOUNT_POINTS:
+ g_value_take_object (value, gst_rtsp_client_get_mount_points (client));
+ break;
+ case PROP_DROP_BACKLOG:
+ g_value_set_boolean (value, priv->drop_backlog);
+ break;
+ case PROP_POST_SESSION_TIMEOUT:
+ g_value_set_int (value, priv->post_session_timeout);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ static void
+ gst_rtsp_client_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (object);
+ GstRTSPClientPrivate *priv = client->priv;
+
+ switch (propid) {
+ case PROP_SESSION_POOL:
+ gst_rtsp_client_set_session_pool (client, g_value_get_object (value));
+ break;
+ case PROP_MOUNT_POINTS:
+ gst_rtsp_client_set_mount_points (client, g_value_get_object (value));
+ break;
+ case PROP_DROP_BACKLOG:
+ g_mutex_lock (&priv->lock);
+ priv->drop_backlog = g_value_get_boolean (value);
+ g_mutex_unlock (&priv->lock);
+ break;
+ case PROP_POST_SESSION_TIMEOUT:
+ g_mutex_lock (&priv->lock);
+ priv->post_session_timeout = g_value_get_int (value);
+ g_mutex_unlock (&priv->lock);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ /**
+ * gst_rtsp_client_new:
+ *
+ * Create a new #GstRTSPClient instance.
+ *
+ * Returns: (transfer full): a new #GstRTSPClient
+ */
+ GstRTSPClient *
+ gst_rtsp_client_new (void)
+ {
+ GstRTSPClient *result;
+
+ result = g_object_new (GST_TYPE_RTSP_CLIENT, NULL);
+
+ return result;
+ }
+
+ static void
+ send_message (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPMessage * message, gboolean close)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+
+ gst_rtsp_message_add_header (message, GST_RTSP_HDR_SERVER,
+ "GStreamer RTSP server");
+
+ /* remove any previous header */
+ gst_rtsp_message_remove_header (message, GST_RTSP_HDR_SESSION, -1);
+
+ /* add the new session header for new session ids */
+ if (ctx->session) {
+ gst_rtsp_message_take_header (message, GST_RTSP_HDR_SESSION,
+ gst_rtsp_session_get_header (ctx->session));
+ }
+
+ if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) {
+ gst_rtsp_message_dump (message);
+ }
+
+ if (close)
+ gst_rtsp_message_add_header (message, GST_RTSP_HDR_CONNECTION, "close");
+
+ if (ctx->request)
+ message->type_data.response.version =
+ ctx->request->type_data.request.version;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SEND_MESSAGE],
+ 0, ctx, message);
+
+ g_mutex_lock (&priv->send_lock);
+ if (priv->send_messages_func) {
+ priv->send_messages_func (client, message, 1, close, priv->send_data);
+ } else if (priv->send_func) {
+ priv->send_func (client, message, close, priv->send_data);
+ }
+ g_mutex_unlock (&priv->send_lock);
+
+ gst_rtsp_message_unset (message);
+ }
+
+ static void
+ send_generic_response (GstRTSPClient * client, GstRTSPStatusCode code,
+ GstRTSPContext * ctx)
+ {
+ gst_rtsp_message_init_response (ctx->response, code,
+ gst_rtsp_status_as_text (code), ctx->request);
+
+ ctx->session = NULL;
+
+ send_message (client, ctx, ctx->response, FALSE);
+ }
+
+ static void
+ send_option_not_supported_response (GstRTSPClient * client,
+ GstRTSPContext * ctx, const gchar * unsupported_options)
+ {
+ GstRTSPStatusCode code = GST_RTSP_STS_OPTION_NOT_SUPPORTED;
+
+ gst_rtsp_message_init_response (ctx->response, code,
+ gst_rtsp_status_as_text (code), ctx->request);
+
+ if (unsupported_options != NULL) {
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_UNSUPPORTED,
+ unsupported_options);
+ }
+
+ ctx->session = NULL;
+
+ send_message (client, ctx, ctx->response, FALSE);
+ }
+
+ static gboolean
+ paths_are_equal (const gchar * path1, const gchar * path2, gint len2)
+ {
+ if (path1 == NULL || path2 == NULL)
+ return FALSE;
+
+ if (strlen (path1) != len2)
+ return FALSE;
+
+ if (strncmp (path1, path2, len2))
+ return FALSE;
+
+ return TRUE;
+ }
+
+ /* this function is called to initially find the media for the DESCRIBE request
+ * but is cached for when the same client (without breaking the connection) is
+ * doing a setup for the exact same url. */
+ static GstRTSPMedia *
+ find_media (GstRTSPClient * client, GstRTSPContext * ctx, gchar * path,
+ gint * matched)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPMediaFactory *factory;
+ GstRTSPMedia *media;
+ gint path_len;
+
+ /* find the longest matching factory for the uri first */
+ if (!(factory = gst_rtsp_mount_points_match (priv->mount_points,
+ path, matched)))
+ goto no_factory;
+
+ ctx->factory = factory;
+
+ if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS))
+ goto no_factory_access;
+
+ if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT))
+ goto not_authorized;
+
+ if (matched)
+ path_len = *matched;
+ else
+ path_len = strlen (path);
+
+ if (!paths_are_equal (priv->path, path, path_len)) {
+ /* remove any previously cached values before we try to construct a new
+ * media for uri */
+ clean_cached_media (client, TRUE);
+
+ /* prepare the media and add it to the pipeline */
+ if (!(media = gst_rtsp_media_factory_construct (factory, ctx->uri)))
+ goto no_media;
+
+ ctx->media = media;
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD)) {
+ GstRTSPThread *thread;
+
+ thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
+ GST_RTSP_THREAD_TYPE_MEDIA, ctx);
+ if (thread == NULL)
+ goto no_thread;
+
+ /* prepare the media */
+ if (!gst_rtsp_media_prepare (media, thread))
+ goto no_prepare;
+ }
+
+ /* now keep track of the uri and the media */
+ priv->path = g_strndup (path, path_len);
+ priv->media = media;
+ } else {
+ /* we have seen this path before, used cached media */
+ media = priv->media;
+ ctx->media = media;
+ GST_INFO ("reusing cached media %p for path %s", media, priv->path);
+ }
+
+ g_object_unref (factory);
+ ctx->factory = NULL;
+
+ if (media)
+ g_object_ref (media);
+
+ return media;
+
+ /* ERRORS */
+ no_factory:
+ {
+ GST_ERROR ("client %p: no factory for path %s", client, path);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return NULL;
+ }
+ no_factory_access:
+ {
+ g_object_unref (factory);
+ ctx->factory = NULL;
+ GST_ERROR ("client %p: not authorized to see factory path %s", client,
+ path);
+ /* error reply is already sent */
+ return NULL;
+ }
+ not_authorized:
+ {
+ g_object_unref (factory);
+ ctx->factory = NULL;
+ GST_ERROR ("client %p: not authorized for factory path %s", client, path);
+ /* error reply is already sent */
+ return NULL;
+ }
+ no_media:
+ {
+ GST_ERROR ("client %p: can't create media", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ g_object_unref (factory);
+ ctx->factory = NULL;
+ return NULL;
+ }
+ no_thread:
+ {
+ GST_ERROR ("client %p: can't create thread", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ g_object_unref (media);
+ ctx->media = NULL;
+ g_object_unref (factory);
+ ctx->factory = NULL;
+ return NULL;
+ }
+ no_prepare:
+ {
+ GST_ERROR ("client %p: can't prepare media", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ g_object_unref (media);
+ ctx->media = NULL;
+ g_object_unref (factory);
+ ctx->factory = NULL;
+ return NULL;
+ }
+ }
+
+ static inline DataSeq *
+ get_data_seq_element (GstRTSPClient * client, guint8 channel)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GArray *data_seqs = priv->data_seqs;
+ gint i = 0;
+
+ while (i < data_seqs->len) {
+ DataSeq *data_seq = &g_array_index (data_seqs, DataSeq, i);
+ if (data_seq->channel == channel)
+ return data_seq;
+ i++;
+ }
+
+ return NULL;
+ }
+
+ static void
+ add_data_seq (GstRTSPClient * client, guint8 channel)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ DataSeq data_seq = {.channel = channel,.seq = 0 };
+
+ if (get_data_seq_element (client, channel) == NULL)
+ g_array_append_val (priv->data_seqs, data_seq);
+ }
+
+ static void
+ set_data_seq (GstRTSPClient * client, guint8 channel, guint seq)
+ {
+ DataSeq *data_seq;
+
+ data_seq = get_data_seq_element (client, channel);
+ g_assert_nonnull (data_seq);
+ data_seq->seq = seq;
+ }
+
+ static guint
+ get_data_seq (GstRTSPClient * client, guint8 channel)
+ {
+ DataSeq *data_seq;
+
+ data_seq = get_data_seq_element (client, channel);
+ g_assert_nonnull (data_seq);
+ return data_seq->seq;
+ }
+
+ static gboolean
+ get_data_channel (GstRTSPClient * client, guint seq, guint8 * channel)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GArray *data_seqs = priv->data_seqs;
+ gint i = 0;
+
+ while (i < data_seqs->len) {
+ DataSeq *data_seq = &g_array_index (data_seqs, DataSeq, i);
+ if (data_seq->seq == seq) {
+ *channel = data_seq->channel;
+ return TRUE;
+ }
+ i++;
+ }
+
+ return FALSE;
+ }
+
+ static gboolean
+ do_close (gpointer user_data)
+ {
+ GstRTSPClient *client = user_data;
+
+ gst_rtsp_client_close (client);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ static gboolean
+ do_send_data (GstBuffer * buffer, guint8 channel, GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPMessage message = { 0 };
+ gboolean ret = TRUE;
+
+ gst_rtsp_message_init_data (&message, channel);
+
+ gst_rtsp_message_set_body_buffer (&message, buffer);
+
+ g_mutex_lock (&priv->send_lock);
+ if (get_data_seq (client, channel) != 0) {
+ GST_WARNING ("already a queued data message for channel %d", channel);
+ g_mutex_unlock (&priv->send_lock);
+ return FALSE;
+ }
+ if (priv->send_messages_func) {
+ ret =
+ priv->send_messages_func (client, &message, 1, FALSE, priv->send_data);
+ } else if (priv->send_func) {
+ ret = priv->send_func (client, &message, FALSE, priv->send_data);
+ }
+ g_mutex_unlock (&priv->send_lock);
+
+ gst_rtsp_message_unset (&message);
+
+ if (!ret) {
+ GSource *idle_src;
+
+ /* close in watch context */
+ idle_src = g_idle_source_new ();
+ g_source_set_callback (idle_src, do_close, client, NULL);
+ g_source_attach (idle_src, priv->watch_context);
+ g_source_unref (idle_src);
+ }
+
+ return ret;
+ }
+
+ static gboolean
+ do_check_back_pressure (guint8 channel, GstRTSPClient * client)
+ {
+ return get_data_seq (client, channel) != 0;
+ }
+
+ static gboolean
+ do_send_data_list (GstBufferList * buffer_list, guint8 channel,
+ GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ gboolean ret = TRUE;
+ guint i, n = gst_buffer_list_length (buffer_list);
+ GstRTSPMessage *messages;
+
+ g_mutex_lock (&priv->send_lock);
+ if (get_data_seq (client, channel) != 0) {
+ GST_WARNING ("already a queued data message for channel %d", channel);
+ g_mutex_unlock (&priv->send_lock);
+ return FALSE;
+ }
+
+ messages = g_newa (GstRTSPMessage, n);
+ memset (messages, 0, sizeof (GstRTSPMessage) * n);
+ for (i = 0; i < n; i++) {
+ GstBuffer *buffer = gst_buffer_list_get (buffer_list, i);
+ gst_rtsp_message_init_data (&messages[i], channel);
+ gst_rtsp_message_set_body_buffer (&messages[i], buffer);
+ }
+
+ if (priv->send_messages_func) {
+ ret =
+ priv->send_messages_func (client, messages, n, FALSE, priv->send_data);
+ } else if (priv->send_func) {
+ for (i = 0; i < n; i++) {
+ ret = priv->send_func (client, &messages[i], FALSE, priv->send_data);
+ if (!ret)
+ break;
+ }
+ }
+ g_mutex_unlock (&priv->send_lock);
+
+ for (i = 0; i < n; i++) {
+ gst_rtsp_message_unset (&messages[i]);
+ }
+
+ if (!ret) {
+ GSource *idle_src;
+
+ /* close in watch context */
+ idle_src = g_idle_source_new ();
+ g_source_set_callback (idle_src, do_close, client, NULL);
+ g_source_attach (idle_src, priv->watch_context);
+ g_source_unref (idle_src);
+ }
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_client_close:
+ * @client: a #GstRTSPClient
+ *
+ * Close the connection of @client and remove all media it was managing.
+ *
+ * Since: 1.4
+ */
+ void
+ gst_rtsp_client_close (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ const gchar *tunnelid;
+
+ GST_DEBUG ("client %p: closing connection", client);
+
+ g_mutex_lock (&priv->watch_lock);
+
+ /* Work around the lack of thread safety of gst_rtsp_connection_close */
+ if (priv->watch) {
+ gst_rtsp_watch_set_flushing (priv->watch, TRUE);
+ }
+
+ if (priv->connection) {
+ if ((tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection))) {
+ g_mutex_lock (&tunnels_lock);
+ /* remove from tunnelids */
+ g_hash_table_remove (tunnels, tunnelid);
+ g_mutex_unlock (&tunnels_lock);
+ }
+ gst_rtsp_connection_flush (priv->connection, TRUE);
+ gst_rtsp_connection_close (priv->connection);
+ }
+
+ if (priv->watch) {
+ GST_DEBUG ("client %p: destroying watch", client);
+ g_source_destroy ((GSource *) priv->watch);
+ priv->watch = NULL;
+ gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
+ gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
+ rtsp_ctrl_timeout_remove (client);
+ }
+
+ g_mutex_unlock (&priv->watch_lock);
+ }
+
+ static gchar *
+ default_make_path_from_uri (GstRTSPClient * client, const GstRTSPUrl * uri)
+ {
+ gchar *path;
+
+ if (uri->query) {
+ path = g_strconcat (uri->abspath, "?", uri->query, NULL);
+ } else {
+ /* normalize rtsp://<IP>:<PORT> to rtsp://<IP>:<PORT>/ */
+ path = g_strdup (uri->abspath[0] ? uri->abspath : "/");
+ }
+
+ return path;
+ }
+
+ /* Default signal handler function for all "pre-command" signals, like
+ * pre-options-request. It just returns the RTSP return code 200.
+ * Subclasses can override this to get another default behaviour.
+ */
+ static GstRTSPStatusCode
+ default_pre_signal_handler (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GST_LOG_OBJECT (client, "returning GST_RTSP_STS_OK");
+ return GST_RTSP_STS_OK;
+ }
+
+ /* The pre-signal accumulator function checks the return value of the signal
+ * handlers. If any of them returns an RTSP status code that does not start
+ * with 2 it will return FALSE, no more signal handlers will be called, and
+ * this last RTSP status code will be the result of the signal emission.
+ */
+ static gboolean
+ pre_signal_accumulator (GSignalInvocationHint * ihint, GValue * return_accu,
+ const GValue * handler_return, gpointer data)
+ {
+ GstRTSPStatusCode handler_value = g_value_get_enum (handler_return);
+ GstRTSPStatusCode accumulated_value = g_value_get_enum (return_accu);
+
+ if (handler_value < 200 || handler_value > 299) {
+ GST_DEBUG ("handler_value : %d, returning FALSE", handler_value);
+ g_value_set_enum (return_accu, handler_value);
+ return FALSE;
+ }
+
+ /* the accumulated value is initiated to 0 by GLib. if current handler value is
+ * bigger then use that instead
+ *
+ * FIXME: Should we prioritize the 2xx codes in a smarter way?
+ * Like, "201 Created" > "250 Low On Storage Space" > "200 OK"?
+ */
+ if (handler_value > accumulated_value)
+ g_value_set_enum (return_accu, handler_value);
+
+ return TRUE;
+ }
+
+ /* The cleanup_transports function is called from handle_teardown_request() to
+ * remove any stream transports from the newly closed session that were added to
+ * priv->transports in handle_setup_request(). This is done to avoid forwarding
+ * data from the client on a channel that we just closed.
+ */
+ static void
+ cleanup_transports (GstRTSPClient * client, GPtrArray * transports)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPStreamTransport *stream_transport;
+ const GstRTSPTransport *rtsp_transport;
+ guint i;
+
+ GST_LOG_OBJECT (client, "potentially removing %u transports",
+ transports->len);
+
+ for (i = 0; i < transports->len; i++) {
+ stream_transport = g_ptr_array_index (transports, i);
+ if (stream_transport == NULL) {
+ GST_LOG_OBJECT (client, "stream transport %u is NULL, continue", i);
+ continue;
+ }
+
+ rtsp_transport = gst_rtsp_stream_transport_get_transport (stream_transport);
+ if (rtsp_transport == NULL) {
+ GST_LOG_OBJECT (client, "RTSP transport %u is NULL, continue", i);
+ continue;
+ }
+
+ /* priv->transport only stores transports where RTP is tunneled over RTSP */
+ if (rtsp_transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP) {
+ if (!g_hash_table_remove (priv->transports,
+ GINT_TO_POINTER (rtsp_transport->interleaved.min))) {
+ GST_WARNING_OBJECT (client,
+ "failed removing transport with key '%d' from priv->transports",
+ rtsp_transport->interleaved.min);
+ }
+ if (!g_hash_table_remove (priv->transports,
+ GINT_TO_POINTER (rtsp_transport->interleaved.max))) {
+ GST_WARNING_OBJECT (client,
+ "failed removing transport with key '%d' from priv->transports",
+ rtsp_transport->interleaved.max);
+ }
+ } else {
+ GST_LOG_OBJECT (client, "transport %u not RTP/RTSP, skip it", i);
+ }
+ }
+ }
+
+ static gboolean
+ handle_teardown_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPClientClass *klass;
+ GstRTSPSession *session;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPStatusCode code;
+ gchar *path;
+ gint matched;
+ gboolean keep_session;
+ GstRTSPStatusCode sig_result;
+ GPtrArray *session_media_transports;
+
+ if (!ctx->session)
+ goto no_session;
+
+ session = ctx->session;
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, ctx->uri);
+
+ /* get a handle to the configuration of the media in the session */
+ sessmedia = gst_rtsp_session_get_media (session, path, &matched);
+ if (!sessmedia)
+ goto not_found;
+
+ /* only aggregate control for now.. */
+ if (path[matched] != '\0')
+ goto no_aggregate;
+
+ g_free (path);
+
+ ctx->sessmedia = sessmedia;
+
+ media = gst_rtsp_session_media_get_media (sessmedia);
+ g_object_ref (media);
+ gst_rtsp_media_lock (media);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_TEARDOWN_REQUEST],
+ 0, ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ /* get a reference to the transports in the session media so we can clean up
+ * our priv->transports before returning */
+ session_media_transports = gst_rtsp_session_media_get_transports (sessmedia);
+
+ /* we emit the signal before closing the connection */
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_TEARDOWN_REQUEST],
+ 0, ctx);
+
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_NULL);
+
+ /* unmanage the media in the session, returns false if all media session
+ * are torn down. */
+ keep_session = gst_rtsp_session_release_media (session, sessmedia);
+
+ /* construct the response now */
+ code = GST_RTSP_STS_OK;
+ gst_rtsp_message_init_response (ctx->response, code,
+ gst_rtsp_status_as_text (code), ctx->request);
+
+ send_message (client, ctx, ctx->response, TRUE);
+
+ if (!keep_session) {
+ /* remove the session */
+ gst_rtsp_session_pool_remove (priv->session_pool, session);
+ }
+
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+
+ /* remove all transports that were present in the session media which we just
+ * unmanaged from the priv->transports array, so we do not try to handle data
+ * on channels that were just closed */
+ cleanup_transports (client, session_media_transports);
+ g_ptr_array_unref (session_media_transports);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_session:
+ {
+ GST_ERROR ("client %p: no session", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri supplied", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ not_found:
+ {
+ GST_ERROR ("client %p: no media for uri", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ g_free (path);
+ return FALSE;
+ }
+ no_aggregate:
+ {
+ GST_ERROR ("client %p: no aggregate path %s", client, path);
+ send_generic_response (client,
+ GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
+ g_free (path);
+ return FALSE;
+ }
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ }
+
+ static GstRTSPResult
+ default_params_set (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPResult res;
+
+ res = gst_rtsp_params_set (client, ctx);
+
+ return res;
+ }
+
+ static GstRTSPResult
+ default_params_get (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPResult res;
+
+ res = gst_rtsp_params_get (client, ctx);
+
+ return res;
+ }
+
+ static gboolean
-handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
++default_handle_get_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPResult res;
+ guint8 *data;
+ guint size;
+ GstRTSPStatusCode sig_result;
+
+ g_signal_emit (client,
+ gst_rtsp_client_signals[SIGNAL_PRE_GET_PARAMETER_REQUEST], 0, ctx,
+ &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ res = gst_rtsp_message_get_body (ctx->request, &data, &size);
+ if (res != GST_RTSP_OK)
+ goto bad_request;
+
+ if (size == 0 || !data || strlen ((char *) data) == 0) {
+ if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
+ GST_ERROR_OBJECT (client, "Using PLAY request for keep-alive is forbidden"
+ " in RTSP 2.0");
+ goto bad_request;
+ }
+
+ /* no body (or only '\0'), keep-alive request */
+ send_generic_response (client, GST_RTSP_STS_OK, ctx);
+ } else {
+ /* there is a body, handle the params */
+ res = GST_RTSP_CLIENT_GET_CLASS (client)->params_get (client, ctx);
+ if (res != GST_RTSP_OK)
+ goto bad_request;
+
+ send_message (client, ctx, ctx->response, FALSE);
+ }
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_GET_PARAMETER_REQUEST],
+ 0, ctx);
+
+ return TRUE;
+
+ /* ERRORS */
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
+ bad_request:
+ {
+ GST_ERROR ("client %p: bad request", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ }
+
+ static gboolean
-handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
++default_handle_set_param_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPResult res;
+ guint8 *data;
+ guint size;
+ GstRTSPStatusCode sig_result;
+
+ g_signal_emit (client,
+ gst_rtsp_client_signals[SIGNAL_PRE_SET_PARAMETER_REQUEST], 0, ctx,
+ &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ res = gst_rtsp_message_get_body (ctx->request, &data, &size);
+ if (res != GST_RTSP_OK)
+ goto bad_request;
+
+ if (size == 0 || !data || strlen ((char *) data) == 0) {
+ /* no body (or only '\0'), keep-alive request */
+ send_generic_response (client, GST_RTSP_STS_OK, ctx);
+ } else {
+ /* there is a body, handle the params */
+ res = GST_RTSP_CLIENT_GET_CLASS (client)->params_set (client, ctx);
+ if (res != GST_RTSP_OK)
+ goto bad_request;
+
+ send_message (client, ctx, ctx->response, FALSE);
+ }
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SET_PARAMETER_REQUEST],
+ 0, ctx);
+
+ return TRUE;
+
+ /* ERRORS */
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
+ bad_request:
+ {
+ GST_ERROR ("client %p: bad request", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ handle_pause_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPSession *session;
+ GstRTSPClientClass *klass;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPStatusCode code;
+ GstRTSPState rtspstate;
+ gchar *path;
+ gint matched;
+ GstRTSPStatusCode sig_result;
+ guint i, n;
+
+ if (!(session = ctx->session))
+ goto no_session;
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, ctx->uri);
+
+ /* get a handle to the configuration of the media in the session */
+ sessmedia = gst_rtsp_session_get_media (session, path, &matched);
+ if (!sessmedia)
+ goto not_found;
+
+ if (path[matched] != '\0')
+ goto no_aggregate;
+
+ g_free (path);
+
+ media = gst_rtsp_session_media_get_media (sessmedia);
+ g_object_ref (media);
+ gst_rtsp_media_lock (media);
+ n = gst_rtsp_media_n_streams (media);
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);
+
+ if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
+ GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET)
+ goto not_supported;
+ }
+
+ ctx->sessmedia = sessmedia;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_PAUSE_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
+ /* the session state must be playing or recording */
+ if (rtspstate != GST_RTSP_STATE_PLAYING &&
+ rtspstate != GST_RTSP_STATE_RECORDING)
+ goto invalid_state;
+
+ /* then pause sending */
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PAUSED);
+
+ /* construct the response now */
+ code = GST_RTSP_STS_OK;
+ gst_rtsp_message_init_response (ctx->response, code,
+ gst_rtsp_status_as_text (code), ctx->request);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ /* the state is now READY */
+ gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PAUSE_REQUEST], 0, ctx);
+
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_session:
+ {
+ GST_ERROR ("client %p: no session", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri supplied", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ not_found:
+ {
+ GST_ERROR ("client %p: no media for uri", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ g_free (path);
+ return FALSE;
+ }
+ no_aggregate:
+ {
+ GST_ERROR ("client %p: no aggregate path %s", client, path);
+ send_generic_response (client,
+ GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
+ g_free (path);
+ return FALSE;
+ }
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ invalid_state:
+ {
+ GST_ERROR ("client %p: not PLAYING or RECORDING", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE,
+ ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ not_supported:
+ {
+ GST_ERROR ("client %p: pausing not supported", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ }
+
+ /* convert @url and @path to a URL used as a content base for the factory
+ * located at @path */
+ static gchar *
+ make_base_url (GstRTSPClient * client, GstRTSPUrl * url, const gchar * path)
+ {
+ GstRTSPUrl tmp;
+ gchar *result;
+ const gchar *trail;
+
+ /* check for trailing '/' and append one */
+ trail = (path[strlen (path) - 1] != '/' ? "/" : "");
+
+ tmp = *url;
+ tmp.user = NULL;
+ tmp.passwd = NULL;
+ tmp.abspath = g_strdup_printf ("%s%s", path, trail);
+ tmp.query = NULL;
+ result = gst_rtsp_url_get_request_uri (&tmp);
+ g_free (tmp.abspath);
+
+ return result;
+ }
+
+ /* Check if the given header of type double is present and, if so,
+ * put it's value in the supplied variable.
+ */
+ static GstRTSPStatusCode
+ parse_header_value_double (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPHeaderField header, gboolean * present, gdouble * value)
+ {
+ GstRTSPResult res;
+ gchar *str;
+ gchar *end;
+
+ res = gst_rtsp_message_get_header (ctx->request, header, &str, 0);
+ if (res == GST_RTSP_OK) {
+ *value = g_ascii_strtod (str, &end);
+ if (end == str)
+ goto parse_header_failed;
+
+ GST_DEBUG ("client %p: got '%s', value %f", client,
+ gst_rtsp_header_as_text (header), *value);
+ *present = TRUE;
+ } else {
+ *present = FALSE;
+ }
+
+ return GST_RTSP_STS_OK;
+
+ parse_header_failed:
+ {
+ GST_ERROR ("client %p: failed parsing '%s' header", client,
+ gst_rtsp_header_as_text (header));
+ return GST_RTSP_STS_BAD_REQUEST;
+ }
+ }
+
+ /* Parse scale and speed headers, if present, and set the rate to
+ * (rate * scale * speed) */
+ static GstRTSPStatusCode
+ parse_scale_and_speed (GstRTSPClient * client, GstRTSPContext * ctx,
+ gboolean * scale_present, gboolean * speed_present, gdouble * rate,
+ GstSeekFlags * flags)
+ {
+ gdouble scale = 1.0;
+ gdouble speed = 1.0;
+ GstRTSPStatusCode status;
+
+ GST_DEBUG ("got rate %f", *rate);
+
+ status = parse_header_value_double (client, ctx, GST_RTSP_HDR_SCALE,
+ scale_present, &scale);
+ if (status != GST_RTSP_STS_OK)
+ return status;
+
+ if (*scale_present) {
+ GST_DEBUG ("got Scale %f", scale);
+ if (scale == 0)
+ goto bad_scale_value;
+ *rate *= scale;
+
+ if (ABS (scale) != 1.0)
+ *flags |= GST_SEEK_FLAG_TRICKMODE;
+ }
+
+ GST_DEBUG ("rate after parsing Scale %f", *rate);
+
+ status = parse_header_value_double (client, ctx, GST_RTSP_HDR_SPEED,
+ speed_present, &speed);
+ if (status != GST_RTSP_STS_OK)
+ return status;
+
+ if (*speed_present) {
+ GST_DEBUG ("got Speed %f", speed);
+ if (speed <= 0)
+ goto bad_speed_value;
+ *rate *= speed;
+ }
+
+ GST_DEBUG ("rate after parsing Speed %f", *rate);
+
+ return status;
+
+ bad_scale_value:
+ {
+ GST_ERROR ("client %p: bad 'Scale' header value (%f)", client, scale);
+ return GST_RTSP_STS_BAD_REQUEST;
+ }
+ bad_speed_value:
+ {
+ GST_ERROR ("client %p: bad 'Speed' header value (%f)", client, speed);
+ return GST_RTSP_STS_BAD_REQUEST;
+ }
+ }
+
+ static GstRTSPStatusCode
+ setup_play_mode (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPRangeUnit * unit, gboolean * scale_present, gboolean * speed_present)
+ {
+ gchar *str;
+ GstRTSPResult res;
+ GstRTSPTimeRange *range = NULL;
+ gdouble rate = 1.0;
+ GstSeekFlags flags = GST_SEEK_FLAG_NONE;
+ GstRTSPClientClass *klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ GstRTSPStatusCode rtsp_status_code;
+ GstClockTime trickmode_interval = 0;
+ gboolean enable_rate_control = TRUE;
+
+ /* parse the range header if we have one */
+ res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_RANGE, &str, 0);
+ if (res == GST_RTSP_OK) {
+ gchar *seek_style = NULL;
+
+ res = gst_rtsp_range_parse (str, &range);
+ if (res != GST_RTSP_OK)
+ goto parse_range_failed;
+
+ *unit = range->unit;
+
+ /* parse seek style header, if present */
+ res = gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_SEEK_STYLE,
+ &seek_style, 0);
+
+ if (res == GST_RTSP_OK) {
+ if (g_strcmp0 (seek_style, "RAP") == 0)
+ flags = GST_SEEK_FLAG_ACCURATE;
+ else if (g_strcmp0 (seek_style, "CoRAP") == 0)
+ flags = GST_SEEK_FLAG_KEY_UNIT;
+ else if (g_strcmp0 (seek_style, "First-Prior") == 0)
+ flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_BEFORE;
+ else if (g_strcmp0 (seek_style, "Next") == 0)
+ flags = GST_SEEK_FLAG_KEY_UNIT & GST_SEEK_FLAG_SNAP_AFTER;
+ else
+ GST_FIXME_OBJECT (client, "Add support for seek style %s", seek_style);
+ } else if (range->min.type == GST_RTSP_TIME_END) {
+ flags = GST_SEEK_FLAG_ACCURATE;
+ } else {
+ flags = GST_SEEK_FLAG_KEY_UNIT;
+ }
+
+ if (seek_style)
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_SEEK_STYLE,
+ seek_style);
+ } else {
+ flags = GST_SEEK_FLAG_ACCURATE;
+ }
+
+ /* check for scale and/or speed headers
+ * we will set the seek rate to (speed * scale) and let the media decide
+ * the resulting scale and speed. in the response we will use rate and applied
+ * rate from the resulting segment as values for the speed and scale headers
+ * respectively */
+ rtsp_status_code = parse_scale_and_speed (client, ctx, scale_present,
+ speed_present, &rate, &flags);
+ if (rtsp_status_code != GST_RTSP_STS_OK)
+ goto scale_speed_failed;
+
+ /* give the application a chance to tweak range, flags, or rate */
+ if (klass->adjust_play_mode != NULL) {
+ rtsp_status_code =
+ klass->adjust_play_mode (client, ctx, &range, &flags, &rate,
+ &trickmode_interval, &enable_rate_control);
+ if (rtsp_status_code != GST_RTSP_STS_OK)
+ goto adjust_play_mode_failed;
+ }
+
+ gst_rtsp_media_set_rate_control (ctx->media, enable_rate_control);
+
+ /* now do the seek with the seek options */
+ gst_rtsp_media_seek_trickmode (ctx->media, range, flags, rate,
+ trickmode_interval);
+ if (range != NULL)
+ gst_rtsp_range_free (range);
+
+ if (gst_rtsp_media_get_status (ctx->media) == GST_RTSP_MEDIA_STATUS_ERROR)
+ goto seek_failed;
+
+ return GST_RTSP_STS_OK;
+
+ parse_range_failed:
+ {
+ GST_ERROR ("client %p: failed parsing range header", client);
+ return GST_RTSP_STS_BAD_REQUEST;
+ }
+ scale_speed_failed:
+ {
+ if (range != NULL)
+ gst_rtsp_range_free (range);
+ GST_ERROR ("client %p: failed parsing Scale or Speed headers", client);
+ return rtsp_status_code;
+ }
+ adjust_play_mode_failed:
+ {
+ GST_ERROR ("client %p: sub class returned bad code (%d)", client,
+ rtsp_status_code);
+ if (range != NULL)
+ gst_rtsp_range_free (range);
+ return rtsp_status_code;
+ }
+ seek_failed:
+ {
+ GST_ERROR ("client %p: seek failed", client);
+ return GST_RTSP_STS_SERVICE_UNAVAILABLE;
+ }
+ }
+
+ static gboolean
-handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
++default_handle_play_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPSession *session;
+ GstRTSPClientClass *klass;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPStatusCode code;
+ GstRTSPUrl *uri;
+ gchar *str;
+ GstRTSPState rtspstate;
+ GstRTSPRangeUnit unit = GST_RTSP_RANGE_NPT;
+ gchar *path, *rtpinfo = NULL;
+ gint matched;
+ GstRTSPStatusCode sig_result;
+ GPtrArray *transports;
+ gboolean scale_present;
+ gboolean speed_present;
+ gdouble rate;
+ gdouble applied_rate;
+
+ if (!(session = ctx->session))
+ goto no_session;
+
+ if (!(uri = ctx->uri))
+ goto no_uri;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, uri);
+
+ /* get a handle to the configuration of the media in the session */
+ sessmedia = gst_rtsp_session_get_media (session, path, &matched);
+ if (!sessmedia)
+ goto not_found;
+
+ if (path[matched] != '\0')
+ goto no_aggregate;
+
+ g_free (path);
+
+ ctx->sessmedia = sessmedia;
+ ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);
+
+ g_object_ref (media);
+ gst_rtsp_media_lock (media);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_PLAY_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY))
+ goto unsupported_mode;
+
+ /* the session state must be playing or ready */
+ rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
+ if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
+ goto invalid_state;
+
+ /* update the pipeline */
+ transports = gst_rtsp_session_media_get_transports (sessmedia);
+ if (!gst_rtsp_media_complete_pipeline (media, transports)) {
+ g_ptr_array_unref (transports);
+ goto pipeline_error;
+ }
+ g_ptr_array_unref (transports);
+
+ /* in play we first unsuspend, media could be suspended from SDP or PAUSED */
+ if (!gst_rtsp_media_unsuspend (media))
+ goto unsuspend_failed;
+
+ code = setup_play_mode (client, ctx, &unit, &scale_present, &speed_present);
+ if (code != GST_RTSP_STS_OK)
+ goto invalid_mode;
+
+ /* grab RTPInfo from the media now */
+ if (gst_rtsp_media_has_completed_sender (media) &&
+ !(rtpinfo = gst_rtsp_session_media_get_rtpinfo (sessmedia)))
+ goto rtp_info_error;
+
+ /* construct the response now */
+ code = GST_RTSP_STS_OK;
+ gst_rtsp_message_init_response (ctx->response, code,
+ gst_rtsp_status_as_text (code), ctx->request);
+
+ /* add the RTP-Info header */
+ if (rtpinfo)
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RTP_INFO,
+ rtpinfo);
+
+ /* add the range */
+ str = gst_rtsp_media_get_range_string (media, TRUE, unit);
+ if (str)
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_RANGE, str);
+
+ if (gst_rtsp_media_has_completed_sender (media)) {
+ /* the scale and speed headers must always be added if they were present in
+ * the request. however, even if they were not, we still add them if
+ * applied_rate or rate deviate from the "normal", i.e. 1.0 */
+ if (!gst_rtsp_media_get_rates (media, &rate, &applied_rate))
+ goto get_rates_error;
+ g_assert (rate != 0 && applied_rate != 0);
+
+ if (scale_present || applied_rate != 1.0)
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_SCALE,
+ g_strdup_printf ("%1.3f", applied_rate));
+
+ if (speed_present || rate != 1.0)
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_SPEED,
+ g_strdup_printf ("%1.3f", rate));
+ }
+
+ if (klass->adjust_play_response) {
+ code = klass->adjust_play_response (client, ctx);
+ if (code != GST_RTSP_STS_OK)
+ goto adjust_play_response_failed;
+ }
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ /* start playing after sending the response */
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING);
+
+ gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PLAY_REQUEST], 0, ctx);
+
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_session:
+ {
+ GST_ERROR ("client %p: no session", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri supplied", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ not_found:
+ {
+ GST_ERROR ("client %p: media not found", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_aggregate:
+ {
+ GST_ERROR ("client %p: no aggregate path %s", client, path);
+ send_generic_response (client,
+ GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
+ g_free (path);
+ return FALSE;
+ }
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ invalid_state:
+ {
+ GST_ERROR ("client %p: not PLAYING or READY", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE,
+ ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ pipeline_error:
+ {
+ GST_ERROR ("client %p: failed to configure the pipeline", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE,
+ ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ unsuspend_failed:
+ {
+ GST_ERROR ("client %p: unsuspend failed", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ invalid_mode:
+ {
+ GST_ERROR ("client %p: seek failed", client);
+ send_generic_response (client, code, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ unsupported_mode:
+ {
+ GST_ERROR ("client %p: media does not support PLAY", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ get_rates_error:
+ {
+ GST_ERROR ("client %p: failed obtaining rate and applied_rate", client);
+ send_generic_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ adjust_play_response_failed:
+ {
+ GST_ERROR ("client %p: failed to adjust play response", client);
+ send_generic_response (client, code, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ rtp_info_error:
+ {
+ GST_ERROR ("client %p: failed to add RTP-Info", client);
+ send_generic_response (client, GST_RTSP_STS_INTERNAL_SERVER_ERROR, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ }
+
+ static void
+ do_keepalive (GstRTSPSession * session)
+ {
+ GST_INFO ("keep session %p alive", session);
+ gst_rtsp_session_touch (session);
+ }
+
+ /* parse @transport and return a valid transport in @tr. only transports
+ * supported by @stream are returned. Returns FALSE if no valid transport
+ * was found. */
+ static gboolean
+ parse_transport (const char *transport, GstRTSPStream * stream,
+ GstRTSPTransport * tr)
+ {
+ gint i;
+ gboolean res;
+ gchar **transports;
+
+ res = FALSE;
+ gst_rtsp_transport_init (tr);
+
+ GST_DEBUG ("parsing transports %s", transport);
+
+ transports = g_strsplit (transport, ",", 0);
+
+ /* loop through the transports, try to parse */
+ for (i = 0; transports[i]; i++) {
+ g_strstrip (transports[i]);
+ res = gst_rtsp_transport_parse (transports[i], tr);
+ if (res != GST_RTSP_OK) {
+ /* no valid transport, search some more */
+ GST_WARNING ("could not parse transport %s", transports[i]);
+ goto next;
+ }
+
+ /* we have a transport, see if it's supported */
+ if (!gst_rtsp_stream_is_transport_supported (stream, tr)) {
+ GST_WARNING ("unsupported transport %s", transports[i]);
+ goto next;
+ }
+
+ /* we have a valid transport */
+ GST_INFO ("found valid transport %s", transports[i]);
+ res = TRUE;
+ break;
+
+ next:
+ gst_rtsp_transport_init (tr);
+ }
+ g_strfreev (transports);
+
+ return res;
+ }
+
+ static gboolean
+ default_configure_client_media (GstRTSPClient * client, GstRTSPMedia * media,
+ GstRTSPStream * stream, GstRTSPContext * ctx)
+ {
+ GstRTSPMessage *request = ctx->request;
+ gchar *blocksize_str;
+
+ if (!gst_rtsp_stream_is_sender (stream))
+ return TRUE;
+
+ if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_BLOCKSIZE,
+ &blocksize_str, 0) == GST_RTSP_OK) {
+ guint64 blocksize;
+ gchar *end;
+
+ blocksize = g_ascii_strtoull (blocksize_str, &end, 10);
+ if (end == blocksize_str)
+ goto parse_failed;
+
+ /* we don't want to change the mtu when this media
+ * can be shared because it impacts other clients */
+ if (gst_rtsp_media_is_shared (media))
+ goto done;
+
+ if (blocksize > G_MAXUINT)
+ blocksize = G_MAXUINT;
+
+ gst_rtsp_stream_set_mtu (stream, blocksize);
+ }
+ done:
+ return TRUE;
+
+ /* ERRORS */
+ parse_failed:
+ {
+ GST_ERROR_OBJECT (client, "failed to parse blocksize");
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ default_configure_client_transport (GstRTSPClient * client,
+ GstRTSPContext * ctx, GstRTSPTransport * ct)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+
+ /* we have a valid transport now, set the destination of the client. */
+ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST ||
+ ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP) {
+ /* allocate UDP ports */
+ GSocketFamily family;
+ gboolean use_client_settings = FALSE;
+
+ family = priv->is_ipv6 ? G_SOCKET_FAMILY_IPV6 : G_SOCKET_FAMILY_IPV4;
+
+ if ((ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) &&
+ gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS) &&
+ (ct->destination != NULL)) {
+
+ if (!gst_rtsp_stream_verify_mcast_ttl (ctx->stream, ct->ttl))
+ goto error_ttl;
+
+ use_client_settings = TRUE;
+ }
+
+ /* We need to allocate the sockets for both families before starting
+ * multiudpsink, otherwise multiudpsink won't accept new clients with
+ * a different family.
+ */
+ /* FIXME: could be more adequately solved by making it possible
+ * to set a socket on multiudpsink after it has already been started */
+ if (!gst_rtsp_stream_allocate_udp_sockets (ctx->stream,
+ G_SOCKET_FAMILY_IPV4, ct, use_client_settings)
+ && family == G_SOCKET_FAMILY_IPV4)
+ goto error_allocating_ports;
+
+ if (!gst_rtsp_stream_allocate_udp_sockets (ctx->stream,
+ G_SOCKET_FAMILY_IPV6, ct, use_client_settings)
+ && family == G_SOCKET_FAMILY_IPV6)
+ goto error_allocating_ports;
+
+ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+ if (use_client_settings) {
+ /* FIXME: the address has been successfully allocated, however, in
+ * the use_client_settings case we need to verify that the allocated
+ * address is the one requested by the client and if this address is
+ * an allowed destination. Verifying this via the address pool in not
+ * the proper way as the address pool should only be used for choosing
+ * the server-selected address/port pairs. */
+ GSocket *rtp_socket;
+ guint ttl;
+
+ rtp_socket =
+ gst_rtsp_stream_get_rtp_multicast_socket (ctx->stream, family);
+ if (rtp_socket == NULL)
+ goto no_socket;
+ ttl = g_socket_get_multicast_ttl (rtp_socket);
+ g_object_unref (rtp_socket);
+ if (ct->ttl < ttl) {
+ /* use the maximum ttl that is requested by multicast clients */
+ GST_DEBUG ("requested ttl %u, but keeping ttl %u", ct->ttl, ttl);
+ ct->ttl = ttl;
+ }
+
+ } else {
+ GstRTSPAddress *addr = NULL;
+
+ g_free (ct->destination);
+ addr = gst_rtsp_stream_get_multicast_address (ctx->stream, family);
+ if (addr == NULL)
+ goto no_address;
+ ct->destination = g_strdup (addr->address);
+ ct->port.min = addr->port;
+ ct->port.max = addr->port + addr->n_ports - 1;
+ ct->ttl = addr->ttl;
+ gst_rtsp_address_free (addr);
+ }
+
+ if (!gst_rtsp_stream_add_multicast_client_address (ctx->stream,
+ ct->destination, ct->port.min, ct->port.max, family))
+ goto error_mcast_transport;
+
+ } else {
+ GstRTSPUrl *url;
+
+ url = gst_rtsp_connection_get_url (priv->connection);
+ g_free (ct->destination);
+ ct->destination = g_strdup (url->host);
+ }
+ } else {
+ GstRTSPUrl *url;
+
+ url = gst_rtsp_connection_get_url (priv->connection);
+ g_free (ct->destination);
+ ct->destination = g_strdup (url->host);
+
+ if (ct->lower_transport & GST_RTSP_LOWER_TRANS_TCP) {
+ GSocket *sock;
+ GSocketAddress *addr;
+
+ sock = gst_rtsp_connection_get_read_socket (priv->connection);
+ if ((addr = g_socket_get_remote_address (sock, NULL))) {
+ /* our read port is the sender port of client */
+ ct->client_port.min =
+ g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
+ g_object_unref (addr);
+ }
+ if ((addr = g_socket_get_local_address (sock, NULL))) {
+ ct->server_port.max =
+ g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
+ g_object_unref (addr);
+ }
+ sock = gst_rtsp_connection_get_write_socket (priv->connection);
+ if ((addr = g_socket_get_remote_address (sock, NULL))) {
+ /* our write port is the receiver port of client */
+ ct->client_port.max =
+ g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
+ g_object_unref (addr);
+ }
+ if ((addr = g_socket_get_local_address (sock, NULL))) {
+ ct->server_port.min =
+ g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
+ g_object_unref (addr);
+ }
+ /* check if the client selected channels for TCP */
+ if (ct->interleaved.min == -1 || ct->interleaved.max == -1) {
+ gst_rtsp_session_media_alloc_channels (ctx->sessmedia,
+ &ct->interleaved);
+ }
+ /* alloc new channels if they are already taken */
+ while (g_hash_table_contains (priv->transports,
+ GINT_TO_POINTER (ct->interleaved.min))
+ || g_hash_table_contains (priv->transports,
+ GINT_TO_POINTER (ct->interleaved.max))) {
+ gst_rtsp_session_media_alloc_channels (ctx->sessmedia,
+ &ct->interleaved);
+ if (ct->interleaved.max > 255)
+ goto error_allocating_channels;
+ }
+ }
+ }
+ return TRUE;
+
+ /* ERRORS */
+ error_ttl:
+ {
+ GST_ERROR_OBJECT (client,
+ "Failed to allocate UDP ports: invalid ttl value");
+ return FALSE;
+ }
+ error_allocating_ports:
+ {
+ GST_ERROR_OBJECT (client, "Failed to allocate UDP ports");
+ return FALSE;
+ }
+ no_address:
+ {
+ GST_ERROR_OBJECT (client, "Failed to acquire address for stream");
+ return FALSE;
+ }
+ no_socket:
+ {
+ GST_ERROR_OBJECT (client, "Failed to get UDP socket");
+ return FALSE;
+ }
+ error_mcast_transport:
+ {
+ GST_ERROR_OBJECT (client, "Failed to add multicast client transport");
+ return FALSE;
+ }
+ error_allocating_channels:
+ {
+ GST_ERROR_OBJECT (client, "Failed to allocate interleaved channels");
+ return FALSE;
+ }
+ }
+
+ static GstRTSPTransport *
+ make_server_transport (GstRTSPClient * client, GstRTSPMedia * media,
+ GstRTSPContext * ctx, GstRTSPTransport * ct)
+ {
+ GstRTSPTransport *st;
+ GInetAddress *addr;
+ GSocketFamily family;
+
+ /* prepare the server transport */
+ gst_rtsp_transport_new (&st);
+
+ st->trans = ct->trans;
+ st->profile = ct->profile;
+ st->lower_transport = ct->lower_transport;
+ st->mode_play = ct->mode_play;
+ st->mode_record = ct->mode_record;
+
+ addr = g_inet_address_new_from_string (ct->destination);
+
+ if (!addr) {
+ GST_ERROR ("failed to get inet addr from client destination");
+ family = G_SOCKET_FAMILY_IPV4;
+ } else {
+ family = g_inet_address_get_family (addr);
+ g_object_unref (addr);
+ addr = NULL;
+ }
+
+ switch (st->lower_transport) {
+ case GST_RTSP_LOWER_TRANS_UDP:
+ st->client_port = ct->client_port;
+ gst_rtsp_stream_get_server_port (ctx->stream, &st->server_port, family);
+ break;
+ case GST_RTSP_LOWER_TRANS_UDP_MCAST:
+ st->port = ct->port;
+ st->destination = g_strdup (ct->destination);
+ st->ttl = ct->ttl;
+ break;
+ case GST_RTSP_LOWER_TRANS_TCP:
+ st->interleaved = ct->interleaved;
+ st->client_port = ct->client_port;
+ st->server_port = ct->server_port;
+ default:
+ break;
+ }
+
+ if ((gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY))
+ gst_rtsp_stream_get_ssrc (ctx->stream, &st->ssrc);
+
+ return st;
+ }
+
+ static void
+ rtsp_ctrl_timeout_remove_unlocked (GstRTSPClientPrivate * priv)
+ {
+ if (priv->rtsp_ctrl_timeout != NULL) {
+ GST_DEBUG ("rtsp control session removed timeout %p.",
+ priv->rtsp_ctrl_timeout);
+ g_source_destroy (priv->rtsp_ctrl_timeout);
+ g_source_unref (priv->rtsp_ctrl_timeout);
+ priv->rtsp_ctrl_timeout = NULL;
+ priv->rtsp_ctrl_timeout_cnt = 0;
+ }
+ }
+
+ static void
+ rtsp_ctrl_timeout_remove (GstRTSPClient * client)
+ {
+ g_mutex_lock (&client->priv->lock);
+ rtsp_ctrl_timeout_remove_unlocked (client->priv);
+ g_mutex_unlock (&client->priv->lock);
+ }
+
+ static void
+ rtsp_ctrl_timeout_destroy_notify (gpointer user_data)
+ {
+ GWeakRef *client_weak_ref = (GWeakRef *) user_data;
+
+ g_weak_ref_clear (client_weak_ref);
+ g_free (client_weak_ref);
+ }
+
+ static gboolean
+ rtsp_ctrl_timeout_cb (gpointer user_data)
+ {
+ gboolean res = G_SOURCE_CONTINUE;
+ GstRTSPClientPrivate *priv;
+ GWeakRef *client_weak_ref = (GWeakRef *) user_data;
+ GstRTSPClient *client = (GstRTSPClient *) g_weak_ref_get (client_weak_ref);
+
+ if (client == NULL) {
+ return G_SOURCE_REMOVE;
+ }
+
+ priv = client->priv;
+ g_mutex_lock (&priv->lock);
+ priv->rtsp_ctrl_timeout_cnt += RTSP_CTRL_CB_INTERVAL;
+
+ if ((priv->rtsp_ctrl_timeout_cnt > RTSP_CTRL_TIMEOUT_VALUE)
+ || (priv->had_session
+ && priv->rtsp_ctrl_timeout_cnt > priv->post_session_timeout)) {
+ GST_DEBUG ("rtsp control session timeout %p expired, closing client.",
+ priv->rtsp_ctrl_timeout);
+ rtsp_ctrl_timeout_remove_unlocked (client->priv);
+
+ res = G_SOURCE_REMOVE;
+ }
+
+ g_mutex_unlock (&priv->lock);
+
+ if (res == G_SOURCE_REMOVE) {
+ gst_rtsp_client_close (client);
+ }
+
+ g_object_unref (client);
+
+ return res;
+ }
+
+ static gchar *
+ stream_make_keymgmt (GstRTSPClient * client, const gchar * location,
+ GstRTSPStream * stream)
+ {
+ gchar *base64, *result = NULL;
+ GstMIKEYMessage *mikey_msg;
+ GstCaps *srtcpparams;
+ GstElement *rtcp_encoder;
+ gint srtcp_cipher, srtp_cipher;
+ gint srtcp_auth, srtp_auth;
+ GstBuffer *key;
+ GType ciphertype, authtype;
+ GEnumClass *cipher_enum, *auth_enum;
+ GEnumValue *srtcp_cipher_value, *srtp_cipher_value, *srtcp_auth_value,
+ *srtp_auth_value;
+
+ rtcp_encoder = gst_rtsp_stream_get_srtp_encoder (stream);
+
+ if (!rtcp_encoder)
+ goto done;
+
+ ciphertype = g_type_from_name ("GstSrtpCipherType");
+ authtype = g_type_from_name ("GstSrtpAuthType");
+
+ cipher_enum = g_type_class_ref (ciphertype);
+ auth_enum = g_type_class_ref (authtype);
+
+ /* We need to bring the encoder to READY so that it generates its key */
+ gst_element_set_state (rtcp_encoder, GST_STATE_READY);
+
+ g_object_get (rtcp_encoder, "rtcp-cipher", &srtcp_cipher, "rtcp-auth",
+ &srtcp_auth, "rtp-cipher", &srtp_cipher, "rtp-auth", &srtp_auth, "key",
+ &key, NULL);
+ g_object_unref (rtcp_encoder);
+
+ srtcp_cipher_value = g_enum_get_value (cipher_enum, srtcp_cipher);
+ srtp_cipher_value = g_enum_get_value (cipher_enum, srtp_cipher);
+ srtcp_auth_value = g_enum_get_value (auth_enum, srtcp_auth);
+ srtp_auth_value = g_enum_get_value (auth_enum, srtp_auth);
+
+ g_type_class_unref (cipher_enum);
+ g_type_class_unref (auth_enum);
+
+ srtcpparams = gst_caps_new_simple ("application/x-srtcp",
+ "srtcp-cipher", G_TYPE_STRING, srtcp_cipher_value->value_nick,
+ "srtcp-auth", G_TYPE_STRING, srtcp_auth_value->value_nick,
+ "srtp-cipher", G_TYPE_STRING, srtp_cipher_value->value_nick,
+ "srtp-auth", G_TYPE_STRING, srtp_auth_value->value_nick,
+ "srtp-key", GST_TYPE_BUFFER, key, NULL);
+
+ mikey_msg = gst_mikey_message_new_from_caps (srtcpparams);
+ if (mikey_msg) {
+ guint send_ssrc;
+
+ gst_rtsp_stream_get_ssrc (stream, &send_ssrc);
+ gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_ssrc, 0);
+
+ base64 = gst_mikey_message_base64_encode (mikey_msg);
+ gst_mikey_message_unref (mikey_msg);
+
+ if (base64) {
+ result = gst_sdp_make_keymgmt (location, base64);
+ g_free (base64);
+ }
+ }
+
+ done:
+ return result;
+ }
+
+ static gboolean
+ handle_setup_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPResult res;
+ GstRTSPUrl *uri;
+ gchar *transport, *keymgmt;
+ GstRTSPTransport *ct, *st;
+ GstRTSPStatusCode code;
+ GstRTSPSession *session;
+ GstRTSPStreamTransport *trans;
+ gchar *trans_str;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPStream *stream;
+ GstRTSPState rtspstate;
+ GstRTSPClientClass *klass;
+ gchar *path, *control = NULL;
+ gint matched;
+ gboolean new_session = FALSE;
+ GstRTSPStatusCode sig_result;
+ gchar *pipelined_request_id = NULL, *accept_range = NULL;
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ uri = ctx->uri;
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, uri);
+
+ /* parse the transport */
+ res =
+ gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_TRANSPORT,
+ &transport, 0);
+ if (res != GST_RTSP_OK)
+ goto no_transport;
+
+ /* Handle Pipelined-requests if using >= 2.0 */
+ if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0)
+ gst_rtsp_message_get_header (ctx->request,
+ GST_RTSP_HDR_PIPELINED_REQUESTS, &pipelined_request_id, 0);
+
+ /* we create the session after parsing stuff so that we don't make
+ * a session for malformed requests */
+ if (priv->session_pool == NULL)
+ goto no_pool;
+
+ session = ctx->session;
+
+ if (session) {
+ g_object_ref (session);
+ /* get a handle to the configuration of the media in the session, this can
+ * return NULL if this is a new url to manage in this session. */
+ sessmedia = gst_rtsp_session_get_media (session, path, &matched);
+ } else {
+ /* we need a new media configuration in this session */
+ sessmedia = NULL;
+ }
+
+ /* we have no session media, find one and manage it */
+ if (sessmedia == NULL) {
+ /* get a handle to the configuration of the media in the session */
+ media = find_media (client, ctx, path, &matched);
+ /* need to suspend the media, if the protocol has changed */
+ if (media != NULL) {
+ gst_rtsp_media_lock (media);
+ gst_rtsp_media_suspend (media);
+ }
+ } else {
+ if ((media = gst_rtsp_session_media_get_media (sessmedia))) {
+ g_object_ref (media);
+ gst_rtsp_media_lock (media);
+ } else {
+ goto media_not_found;
+ }
+ }
+ /* no media, not found then */
+ if (media == NULL)
+ goto media_not_found_no_reply;
+
+ if (path[matched] == '\0') {
+ if (gst_rtsp_media_n_streams (media) == 1) {
+ stream = gst_rtsp_media_get_stream (media, 0);
+ } else {
+ goto control_not_found;
+ }
+ } else {
+ /* path is what matched. */
+ gchar *newpath = g_strndup (path, matched);
+ /* control is remainder */
+ if (matched == 1 && path[0] == '/')
+ control = g_strdup (&path[1]);
+ else
+ control = g_strdup (&path[matched + 1]);
+
+ g_free (path);
+ path = newpath;
+
+ /* find the stream now using the control part */
+ stream = gst_rtsp_media_find_stream (media, control);
+ }
+
+ if (stream == NULL)
+ goto stream_not_found;
+
+ /* now we have a uri identifying a valid media and stream */
+ ctx->stream = stream;
+ ctx->media = media;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_SETUP_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ if (session == NULL) {
+ /* create a session if this fails we probably reached our session limit or
+ * something. */
+ if (!(session = gst_rtsp_session_pool_create (priv->session_pool)))
+ goto service_unavailable;
+
+ /* Pipelined requests should be cleared between sessions */
+ g_hash_table_remove_all (priv->pipelined_requests);
+
+ /* make sure this client is closed when the session is closed */
+ client_watch_session (client, session);
+
+ new_session = TRUE;
+ /* signal new session */
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_NEW_SESSION], 0,
+ session);
+
+ ctx->session = session;
+ }
+
+ if (pipelined_request_id) {
+ g_hash_table_insert (client->priv->pipelined_requests,
+ g_strdup (pipelined_request_id),
+ g_strdup (gst_rtsp_session_get_sessionid (session)));
+ }
+ /* Remember that we had at least one session in the past */
+ priv->had_session = TRUE;
+ rtsp_ctrl_timeout_remove (client);
+
+ if (!klass->configure_client_media (client, media, stream, ctx))
+ goto configure_media_failed_no_reply;
+
+ gst_rtsp_transport_new (&ct);
+
+ /* parse and find a usable supported transport */
+ if (!parse_transport (transport, stream, ct))
+ goto unsupported_transports;
+
+ if ((ct->mode_play
+ && !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY)) || (ct->mode_record
+ && !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD)))
+ goto unsupported_mode;
+
+ /* parse the keymgmt */
+ if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_KEYMGMT,
+ &keymgmt, 0) == GST_RTSP_OK) {
+ if (!gst_rtsp_stream_handle_keymgmt (ctx->stream, keymgmt))
+ goto keymgmt_error;
+ }
+
+ if (gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
+ &accept_range, 0) == GST_RTSP_OK) {
+ GEnumValue *runit = NULL;
+ gint i;
+ gchar **valid_ranges;
+ GEnumClass *runit_class = g_type_class_ref (GST_TYPE_RTSP_RANGE_UNIT);
+
+ gst_rtsp_message_dump (ctx->request);
+ valid_ranges = g_strsplit (accept_range, ",", -1);
+
+ for (i = 0; valid_ranges[i]; i++) {
+ gchar *range = valid_ranges[i];
+
+ while (*range == ' ')
+ range++;
+
+ runit = g_enum_get_value_by_nick (runit_class, range);
+ if (runit)
+ break;
+ }
+ g_strfreev (valid_ranges);
+ g_type_class_unref (runit_class);
+
+ if (!runit)
+ goto unsupported_range_unit;
+ }
+
+ if (sessmedia == NULL) {
+ /* manage the media in our session now, if not done already */
+ sessmedia =
+ gst_rtsp_session_manage_media (session, path, g_object_ref (media));
+ /* if we stil have no media, error */
+ if (sessmedia == NULL)
+ goto sessmedia_unavailable;
+
+ /* don't cache media anymore */
+ clean_cached_media (client, FALSE);
+ }
+
+ ctx->sessmedia = sessmedia;
+
+ /* update the client transport */
+ if (!klass->configure_client_transport (client, ctx, ct))
+ goto unsupported_client_transport;
+
+ /* set in the session media transport */
+ trans = gst_rtsp_session_media_set_transport (sessmedia, stream, ct);
+
+ ctx->trans = trans;
+
+ /* configure the url used to set this transport, this we will use when
+ * generating the response for the PLAY request */
+ gst_rtsp_stream_transport_set_url (trans, uri);
+ /* configure keepalive for this transport */
+ gst_rtsp_stream_transport_set_keepalive (trans,
+ (GstRTSPKeepAliveFunc) do_keepalive, session, NULL);
+
+ if (ct->lower_transport == GST_RTSP_LOWER_TRANS_TCP) {
+ /* our callbacks to send data on this TCP connection */
+ gst_rtsp_stream_transport_set_callbacks (trans,
+ (GstRTSPSendFunc) do_send_data,
+ (GstRTSPSendFunc) do_send_data, client, NULL);
+ gst_rtsp_stream_transport_set_list_callbacks (trans,
+ (GstRTSPSendListFunc) do_send_data_list,
+ (GstRTSPSendListFunc) do_send_data_list, client, NULL);
+
+ gst_rtsp_stream_transport_set_back_pressure_callback (trans,
+ (GstRTSPBackPressureFunc) do_check_back_pressure, client, NULL);
+
+ g_hash_table_insert (priv->transports,
+ GINT_TO_POINTER (ct->interleaved.min), trans);
+ g_object_ref (trans);
+ g_hash_table_insert (priv->transports,
+ GINT_TO_POINTER (ct->interleaved.max), trans);
+ g_object_ref (trans);
+ add_data_seq (client, ct->interleaved.min);
+ add_data_seq (client, ct->interleaved.max);
+ }
+
+ /* 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 */
+ code = GST_RTSP_STS_OK;
+ gst_rtsp_message_init_response (ctx->response, code,
+ gst_rtsp_status_as_text (code), ctx->request);
+
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_TRANSPORT,
+ trans_str);
+ g_free (trans_str);
+
+ if (pipelined_request_id)
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_PIPELINED_REQUESTS,
+ pipelined_request_id);
+
+ if (ctx->request->type_data.request.version >= GST_RTSP_VERSION_2_0) {
+ GstClockTimeDiff seekable = gst_rtsp_media_seekable (media);
+ GString *media_properties = g_string_new (NULL);
+
+ if (seekable == -1)
+ g_string_append (media_properties,
+ "No-Seeking,Time-Progressing,Time-Duration=0.0");
+ else if (seekable == 0)
+ g_string_append (media_properties, "Beginning-Only");
+ else if (seekable == G_MAXINT64)
+ g_string_append (media_properties, "Random-Access");
+ else
+ g_string_append_printf (media_properties,
+ "Random-Access=%f, Unlimited, Immutable",
+ (gdouble) seekable / GST_SECOND);
+
+ gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_MEDIA_PROPERTIES,
+ media_properties->str);
+ g_string_free (media_properties, TRUE);
+ /* TODO Check how Accept-Ranges should be filled */
+ gst_rtsp_message_add_header (ctx->request, GST_RTSP_HDR_ACCEPT_RANGES,
+ "npt, clock, smpte, clock");
+ }
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ /* update the state */
+ rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
+ switch (rtspstate) {
+ case GST_RTSP_STATE_PLAYING:
+ case GST_RTSP_STATE_RECORDING:
+ case GST_RTSP_STATE_READY:
+ /* no state change */
+ break;
+ default:
+ gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_READY);
+ break;
+ }
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_SETUP_REQUEST], 0, ctx);
+
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ g_object_unref (session);
+ g_free (path);
+ g_free (control);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ no_transport:
+ {
+ GST_ERROR ("client %p: no transport", client);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
+ goto cleanup_path;
+ }
+ no_pool:
+ {
+ GST_ERROR ("client %p: no session pool configured", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ goto cleanup_path;
+ }
+ media_not_found_no_reply:
+ {
+ GST_ERROR ("client %p: media '%s' not found", client, path);
+ /* error reply is already sent */
+ goto cleanup_session;
+ }
+ media_not_found:
+ {
+ GST_ERROR ("client %p: media '%s' not found", client, path);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ goto cleanup_session;
+ }
+ control_not_found:
+ {
+ GST_ERROR ("client %p: no control in path '%s'", client, path);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ goto cleanup_session;
+ }
+ stream_not_found:
+ {
+ GST_ERROR ("client %p: stream '%s' not found", client,
+ GST_STR_NULL (control));
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ goto cleanup_session;
+ }
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ goto cleanup_path;
+ }
+ service_unavailable:
+ {
+ GST_ERROR ("client %p: can't create session", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ goto cleanup_session;
+ }
+ sessmedia_unavailable:
+ {
+ GST_ERROR ("client %p: can't create session media", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ goto cleanup_transport;
+ }
+ configure_media_failed_no_reply:
+ {
+ GST_ERROR ("client %p: configure_media failed", client);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ /* error reply is already sent */
+ goto cleanup_session;
+ }
+ unsupported_transports:
+ {
+ GST_ERROR ("client %p: unsupported transports", client);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
+ goto cleanup_transport;
+ }
+ unsupported_client_transport:
+ {
+ GST_ERROR ("client %p: unsupported client transport", client);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
+ goto cleanup_transport;
+ }
+ unsupported_mode:
+ {
+ GST_ERROR ("client %p: unsupported mode (media play: %d, media record: %d, "
+ "mode play: %d, mode record: %d)", client,
+ ! !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY),
+ ! !(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD), ct->mode_play, ct->mode_record);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_TRANSPORT, ctx);
+ goto cleanup_transport;
+ }
+ unsupported_range_unit:
+ {
+ GST_ERROR ("Client %p: does not support any range format we support",
+ client);
+ send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx);
+ goto cleanup_transport;
+ }
+ keymgmt_error:
+ {
+ GST_ERROR ("client %p: keymgmt error", client);
+ send_generic_response (client, GST_RTSP_STS_KEY_MANAGEMENT_FAILURE, ctx);
+ goto cleanup_transport;
+ }
+ {
+ cleanup_transport:
+ gst_rtsp_transport_free (ct);
+ if (media) {
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ }
+ cleanup_session:
+ if (new_session)
+ gst_rtsp_session_pool_remove (priv->session_pool, session);
+ if (session)
+ g_object_unref (session);
+ cleanup_path:
+ g_free (path);
+ g_free (control);
+ return FALSE;
+ }
+ }
+
+ static GstSDPMessage *
+ create_sdp (GstRTSPClient * client, GstRTSPMedia * media)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstSDPMessage *sdp;
+ GstSDPInfo info;
+ const gchar *proto;
+ guint64 session_id_tmp;
+ gchar session_id[21];
+
+ gst_sdp_message_new (&sdp);
+
+ /* some standard things first */
+ gst_sdp_message_set_version (sdp, "0");
+
+ if (priv->is_ipv6)
+ proto = "IP6";
+ else
+ proto = "IP4";
+
+ session_id_tmp = (((guint64) g_random_int ()) << 32) | g_random_int ();
+ g_snprintf (session_id, sizeof (session_id), "%" G_GUINT64_FORMAT,
+ session_id_tmp);
+
+ gst_sdp_message_set_origin (sdp, "-", session_id, "1", "IN", proto,
+ priv->server_ip);
+
+ gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer");
+ gst_sdp_message_set_information (sdp, "rtsp-server");
+ gst_sdp_message_add_time (sdp, "0", "0", NULL);
+ gst_sdp_message_add_attribute (sdp, "tool", "GStreamer");
+ gst_sdp_message_add_attribute (sdp, "type", "broadcast");
+ gst_sdp_message_add_attribute (sdp, "control", "*");
+
+ info.is_ipv6 = priv->is_ipv6;
+ info.server_ip = priv->server_ip;
+
+ /* create an SDP for the media object */
+ if (!gst_rtsp_media_setup_sdp (media, sdp, &info))
+ goto no_sdp;
+
+ return sdp;
+
+ /* ERRORS */
+ no_sdp:
+ {
+ GST_ERROR ("client %p: could not create SDP", client);
+ gst_sdp_message_free (sdp);
+ return NULL;
+ }
+ }
+
+ /* for the describe we must generate an SDP */
+ static gboolean
+ handle_describe_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPResult res;
+ GstSDPMessage *sdp;
+ guint i;
+ gchar *path, *str;
+ GstRTSPMedia *media;
+ GstRTSPClientClass *klass;
+ GstRTSPStatusCode sig_result;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_DESCRIBE_REQUEST],
+ 0, ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ /* check what kind of format is accepted, we don't really do anything with it
+ * and always return SDP for now. */
+ for (i = 0;; i++) {
+ gchar *accept;
+
+ res =
+ gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_ACCEPT,
+ &accept, i);
+ if (res == GST_RTSP_ENOTIMPL)
+ break;
+
+ if (g_ascii_strcasecmp (accept, "application/sdp") == 0)
+ break;
+ }
+
+ if (!priv->mount_points)
+ goto no_mount_points;
+
+ if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
+ goto no_path;
+
+ /* find the media object for the uri */
+ if (!(media = find_media (client, ctx, path, NULL)))
+ goto no_media;
+
+ gst_rtsp_media_lock (media);
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_PLAY))
+ goto unsupported_mode;
+
+ /* create an SDP for the media object on this client */
+ if (!(sdp = klass->create_sdp (client, media)))
+ goto no_sdp;
+
+ /* we suspend after the describe */
+ gst_rtsp_media_suspend (media);
+
+ 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_CONTENT_TYPE,
+ "application/sdp");
+
+ /* content base for some clients that might screw up creating the setup uri */
+ str = make_base_url (client, ctx->uri, path);
+ g_free (path);
+
+ GST_INFO ("adding content-base: %s", str);
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_CONTENT_BASE, str);
+
+ /* add SDP to the response body */
+ str = gst_sdp_message_as_text (sdp);
+ gst_rtsp_message_take_body (ctx->response, (guint8 *) str, strlen (str));
+ gst_sdp_message_free (sdp);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_DESCRIBE_REQUEST],
+ 0, ctx);
+
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+
+ return TRUE;
+
+ /* ERRORS */
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ no_mount_points:
+ {
+ GST_ERROR ("client %p: no mount points configured", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_path:
+ {
+ GST_ERROR ("client %p: can't find path for url", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_media:
+ {
+ GST_ERROR ("client %p: no media", client);
+ g_free (path);
+ /* error reply is already sent */
+ return FALSE;
+ }
+ unsupported_mode:
+ {
+ GST_ERROR ("client %p: media does not support DESCRIBE", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ g_free (path);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ no_sdp:
+ {
+ GST_ERROR ("client %p: can't create SDP", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ g_free (path);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ handle_sdp (GstRTSPClient * client, GstRTSPContext * ctx, GstRTSPMedia * media,
+ GstSDPMessage * sdp)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPThread *thread;
+
+ /* create an SDP for the media object */
+ if (!gst_rtsp_media_handle_sdp (media, sdp))
+ goto unhandled_sdp;
+
+ thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
+ GST_RTSP_THREAD_TYPE_MEDIA, ctx);
+ if (thread == NULL)
+ goto no_thread;
+
+ /* prepare the media */
+ if (!gst_rtsp_media_prepare (media, thread))
+ goto no_prepare;
+
+ return TRUE;
+
+ /* ERRORS */
+ unhandled_sdp:
+ {
+ GST_ERROR ("client %p: could not handle SDP", client);
+ return FALSE;
+ }
+ no_thread:
+ {
+ GST_ERROR ("client %p: can't create thread", client);
+ return FALSE;
+ }
+ no_prepare:
+ {
+ GST_ERROR ("client %p: can't prepare media", client);
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ handle_announce_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPClientClass *klass;
+ GstSDPResult sres;
+ GstSDPMessage *sdp;
+ GstRTSPMedia *media;
+ gchar *path, *cont = NULL;
+ guint8 *data;
+ guint size;
+ GstRTSPStatusCode sig_result;
+ guint i, n_streams;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+
+ if (!ctx->uri)
+ goto no_uri;
+
+ if (!priv->mount_points)
+ goto no_mount_points;
+
+ /* check if reply is SDP */
+ gst_rtsp_message_get_header (ctx->request, GST_RTSP_HDR_CONTENT_TYPE, &cont,
+ 0);
+ /* could not be set but since the request returned OK, we assume it
+ * was SDP, else check it. */
+ if (cont) {
+ if (g_ascii_strcasecmp (cont, "application/sdp") != 0)
+ goto wrong_content_type;
+ }
+
+ /* get message body and parse as SDP */
+ gst_rtsp_message_get_body (ctx->request, &data, &size);
+ if (data == NULL || size == 0)
+ goto no_message;
+
+ GST_DEBUG ("client %p: parse SDP...", client);
+ gst_sdp_message_new (&sdp);
+ sres = gst_sdp_message_parse_buffer (data, size, sdp);
+ if (sres != GST_SDP_OK)
+ goto sdp_parse_failed;
+
+ if (!(path = gst_rtsp_mount_points_make_path (priv->mount_points, ctx->uri)))
+ goto no_path;
+
+ /* find the media object for the uri */
+ if (!(media = find_media (client, ctx, path, NULL)))
+ goto no_media;
+
+ ctx->media = media;
+ gst_rtsp_media_lock (media);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_ANNOUNCE_REQUEST],
+ 0, ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD))
+ goto unsupported_mode;
+
+ /* Tell client subclass about the media */
+ if (!klass->handle_sdp (client, ctx, media, sdp))
+ goto unhandled_sdp;
+
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+
+ n_streams = gst_rtsp_media_n_streams (media);
+ for (i = 0; i < n_streams; i++) {
+ GstRTSPStream *stream = gst_rtsp_media_get_stream (media, i);
+ gchar *uri, *location, *keymgmt;
+
+ uri = gst_rtsp_url_get_request_uri (ctx->uri);
+ location = g_strdup_printf ("%s/stream=%d", uri, i);
+ keymgmt = stream_make_keymgmt (client, location, stream);
+
+ if (keymgmt)
+ gst_rtsp_message_take_header (ctx->response, GST_RTSP_HDR_KEYMGMT,
+ keymgmt);
+
+ g_free (location);
+ g_free (uri);
+ }
+
+ /* we suspend after the announce */
+ gst_rtsp_media_suspend (media);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_ANNOUNCE_REQUEST],
+ 0, ctx);
+
+ gst_sdp_message_free (sdp);
+ g_free (path);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+
+ return TRUE;
+
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ no_mount_points:
+ {
+ GST_ERROR ("client %p: no mount points configured", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_path:
+ {
+ GST_ERROR ("client %p: can't find path for url", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+ wrong_content_type:
+ {
+ GST_ERROR ("client %p: unknown content type", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ no_message:
+ {
+ GST_ERROR ("client %p: can't find SDP message", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ sdp_parse_failed:
+ {
+ GST_ERROR ("client %p: failed to parse SDP message", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+ no_media:
+ {
+ GST_ERROR ("client %p: no media", client);
+ g_free (path);
+ /* error reply is already sent */
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_sdp_message_free (sdp);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ return FALSE;
+ }
+ unsupported_mode:
+ {
+ GST_ERROR ("client %p: media does not support ANNOUNCE", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ g_free (path);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+ unhandled_sdp:
+ {
+ GST_ERROR ("client %p: can't handle SDP", client);
+ send_generic_response (client, GST_RTSP_STS_UNSUPPORTED_MEDIA_TYPE, ctx);
+ g_free (path);
+ gst_rtsp_media_unlock (media);
+ g_object_unref (media);
+ gst_sdp_message_free (sdp);
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ handle_record_request (GstRTSPClient * client, GstRTSPContext * ctx)
+ {
+ GstRTSPSession *session;
+ GstRTSPClientClass *klass;
+ GstRTSPSessionMedia *sessmedia;
+ GstRTSPMedia *media;
+ GstRTSPUrl *uri;
+ GstRTSPState rtspstate;
+ gchar *path;
+ gint matched;
+ GstRTSPStatusCode sig_result;
+ GPtrArray *transports;
+
+ if (!(session = ctx->session))
+ goto no_session;
+
+ if (!(uri = ctx->uri))
+ goto no_uri;
+
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ path = klass->make_path_from_uri (client, uri);
+
+ /* get a handle to the configuration of the media in the session */
+ sessmedia = gst_rtsp_session_get_media (session, path, &matched);
+ if (!sessmedia)
+ goto not_found;
+
+ if (path[matched] != '\0')
+ goto no_aggregate;
+
+ g_free (path);
+
+ ctx->sessmedia = sessmedia;
+ ctx->media = media = gst_rtsp_session_media_get_media (sessmedia);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_RECORD_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ if (!(gst_rtsp_media_get_transport_mode (media) &
+ GST_RTSP_TRANSPORT_MODE_RECORD))
+ goto unsupported_mode;
+
+ /* the session state must be playing or ready */
+ rtspstate = gst_rtsp_session_media_get_rtsp_state (sessmedia);
+ if (rtspstate != GST_RTSP_STATE_PLAYING && rtspstate != GST_RTSP_STATE_READY)
+ goto invalid_state;
+
+ /* update the pipeline */
+ transports = gst_rtsp_session_media_get_transports (sessmedia);
+ if (!gst_rtsp_media_complete_pipeline (media, transports)) {
+ g_ptr_array_unref (transports);
+ goto pipeline_error;
+ }
+ g_ptr_array_unref (transports);
+
+ /* in record we first unsuspend, media could be suspended from SDP or PAUSED */
+ if (!gst_rtsp_media_unsuspend (media))
+ goto unsuspend_failed;
+
+ gst_rtsp_message_init_response (ctx->response, GST_RTSP_STS_OK,
+ gst_rtsp_status_as_text (GST_RTSP_STS_OK), ctx->request);
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ /* start playing after sending the response */
+ gst_rtsp_session_media_set_state (sessmedia, GST_STATE_PLAYING);
+
+ gst_rtsp_session_media_set_rtsp_state (sessmedia, GST_RTSP_STATE_PLAYING);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_RECORD_REQUEST], 0,
+ ctx);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_session:
+ {
+ GST_ERROR ("client %p: no session", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_uri:
+ {
+ GST_ERROR ("client %p: no uri supplied", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ return FALSE;
+ }
+ not_found:
+ {
+ GST_ERROR ("client %p: media not found", client);
+ send_generic_response (client, GST_RTSP_STS_NOT_FOUND, ctx);
+ return FALSE;
+ }
+ no_aggregate:
+ {
+ GST_ERROR ("client %p: no aggregate path %s", client, path);
+ send_generic_response (client,
+ GST_RTSP_STS_ONLY_AGGREGATE_OPERATION_ALLOWED, ctx);
+ g_free (path);
+ return FALSE;
+ }
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ return FALSE;
+ }
+ unsupported_mode:
+ {
+ GST_ERROR ("client %p: media does not support RECORD", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_ALLOWED, ctx);
+ return FALSE;
+ }
+ invalid_state:
+ {
+ GST_ERROR ("client %p: not PLAYING or READY", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE,
+ ctx);
+ return FALSE;
+ }
+ pipeline_error:
+ {
+ GST_ERROR ("client %p: failed to configure the pipeline", client);
+ send_generic_response (client, GST_RTSP_STS_METHOD_NOT_VALID_IN_THIS_STATE,
+ ctx);
+ return FALSE;
+ }
+ unsuspend_failed:
+ {
+ GST_ERROR ("client %p: unsuspend failed", client);
+ send_generic_response (client, GST_RTSP_STS_SERVICE_UNAVAILABLE, ctx);
+ return FALSE;
+ }
+ }
+
+ static gboolean
- handle_options_request (client, ctx, version);
++default_handle_options_request (GstRTSPClient * client, GstRTSPContext * ctx,
+ GstRTSPVersion version)
+ {
+ GstRTSPMethod options;
+ gchar *str;
+ GstRTSPStatusCode sig_result;
+
+ options = GST_RTSP_DESCRIBE |
+ GST_RTSP_OPTIONS |
+ GST_RTSP_PAUSE |
+ GST_RTSP_PLAY |
+ GST_RTSP_SETUP |
+ GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN;
+
+ if (version < GST_RTSP_VERSION_2_0) {
+ options |= GST_RTSP_RECORD;
+ options |= GST_RTSP_ANNOUNCE;
+ }
+
+ str = gst_rtsp_options_as_text (options);
+
+ 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);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_PRE_OPTIONS_REQUEST], 0,
+ ctx, &sig_result);
+ if (sig_result != GST_RTSP_STS_OK) {
+ goto sig_failed;
+ }
+
+ send_message (client, ctx, ctx->response, FALSE);
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_OPTIONS_REQUEST],
+ 0, ctx);
+
+ return TRUE;
+
+ /* ERRORS */
+ sig_failed:
+ {
+ GST_ERROR ("client %p: pre signal returned error: %s", client,
+ gst_rtsp_status_as_text (sig_result));
+ send_generic_response (client, sig_result, ctx);
+ gst_rtsp_message_free (ctx->response);
+ return FALSE;
+ }
+ }
+
+ /* remove duplicate and trailing '/' */
+ static void
+ sanitize_uri (GstRTSPUrl * uri)
+ {
+ gint i, len;
+ gchar *s, *d;
+ gboolean have_slash, prev_slash;
+
+ s = d = uri->abspath;
+ len = strlen (uri->abspath);
+
+ prev_slash = FALSE;
+
+ for (i = 0; i < len; i++) {
+ have_slash = s[i] == '/';
+ *d = s[i];
+ if (!have_slash || !prev_slash)
+ d++;
+ prev_slash = have_slash;
+ }
+ len = d - uri->abspath;
+ /* don't remove the first slash if that's the only thing left */
+ if (len > 1 && *(d - 1) == '/')
+ d--;
+ *d = '\0';
+ }
+
+ /* is called when the session is removed from its session pool. */
+ static void
+ client_session_removed (GstRTSPSessionPool * pool, GstRTSPSession * session,
+ GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GSource *timer_src;
+
+ GST_INFO ("client %p: session %p removed", client, session);
+
+ g_mutex_lock (&priv->lock);
+ client_unwatch_session (client, session, NULL);
+
+ if (!priv->sessions && priv->rtsp_ctrl_timeout == NULL) {
+ if (priv->post_session_timeout > 0) {
+ GWeakRef *client_weak_ref = g_new (GWeakRef, 1);
+ timer_src = g_timeout_source_new_seconds (RTSP_CTRL_CB_INTERVAL);
+
+ g_weak_ref_init (client_weak_ref, client);
+ g_source_set_callback (timer_src, rtsp_ctrl_timeout_cb, client_weak_ref,
+ rtsp_ctrl_timeout_destroy_notify);
+ priv->rtsp_ctrl_timeout_cnt = 0;
+ g_source_attach (timer_src, priv->watch_context);
+ priv->rtsp_ctrl_timeout = timer_src;
+ GST_DEBUG ("rtsp control setting up connection timeout %p.",
+ priv->rtsp_ctrl_timeout);
+ g_mutex_unlock (&priv->lock);
+ } else if (priv->post_session_timeout == 0) {
+ g_mutex_unlock (&priv->lock);
+ gst_rtsp_client_close (client);
+ } else {
+ g_mutex_unlock (&priv->lock);
+ }
+ } else {
+ g_mutex_unlock (&priv->lock);
+ }
+ }
+
+ /* Check for Require headers. Returns TRUE if there are no Require headers,
+ * otherwise lets the application decide which headers are supported.
+ * By default all headers are unsupported.
+ * If there are unsupported options, FALSE will be returned together with
+ * a newly-allocated string of (comma-separated) unsupported options in
+ * the unsupported_reqs variable.
+ *
+ * There may be multiple Require headers, but we must send one single
+ * Unsupported header with all the unsupported options as response. If
+ * an incoming Require header contained a comma-separated list of options
+ * GstRtspConnection will already have split that list up into multiple
+ * headers.
+ */
+ static gboolean
+ check_request_requirements (GstRTSPContext * ctx, gchar ** unsupported_reqs)
+ {
+ GstRTSPResult res;
+ GPtrArray *arr = NULL;
+ GstRTSPMessage *msg = ctx->request;
+ gchar *reqs = NULL;
+ gint i;
+ gchar *sig_result = NULL;
+ gboolean result = TRUE;
+
+ i = 0;
+ do {
+ res = gst_rtsp_message_get_header (msg, GST_RTSP_HDR_REQUIRE, &reqs, i++);
+
+ if (res == GST_RTSP_ENOTIMPL)
+ break;
+
+ if (arr == NULL)
+ arr = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+
+ g_ptr_array_add (arr, g_strdup (reqs));
+ }
+ while (TRUE);
+
+ /* if we don't have any Require headers at all, all is fine */
+ if (i == 1)
+ return TRUE;
+
+ /* otherwise we've now processed at all the Require headers */
+ g_ptr_array_add (arr, NULL);
+
+ g_signal_emit (ctx->client,
+ gst_rtsp_client_signals[SIGNAL_CHECK_REQUIREMENTS], 0, ctx,
+ (gchar **) arr->pdata, &sig_result);
+
+ if (sig_result == NULL) {
+ /* no supported options, just report all of the required ones as
+ * unsupported */
+ *unsupported_reqs = g_strjoinv (", ", (gchar **) arr->pdata);
+ result = FALSE;
+ goto done;
+ }
+
+ if (strlen (sig_result) == 0)
+ g_free (sig_result);
+ else {
+ *unsupported_reqs = sig_result;
+ result = FALSE;
+ }
+
+ done:
+ g_ptr_array_unref (arr);
+ return result;
+ }
+
+ static void
+ handle_request (GstRTSPClient * client, GstRTSPMessage * request)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPMethod method;
+ const gchar *uristr;
+ GstRTSPUrl *uri = NULL;
+ GstRTSPVersion version;
+ GstRTSPResult res;
+ GstRTSPSession *session = NULL;
+ GstRTSPContext sctx = { NULL }, *ctx;
+ GstRTSPMessage response = { 0 };
+ gchar *unsupported_reqs = NULL;
+ gchar *sessid = NULL, *pipelined_request_id = NULL;
++ GstRTSPClientClass *klass;
+
++ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+ if (!(ctx = gst_rtsp_context_get_current ())) {
+ ctx = &sctx;
+ ctx->auth = priv->auth;
+ gst_rtsp_context_push_current (ctx);
+ }
+
+ ctx->conn = priv->connection;
+ ctx->client = client;
+ ctx->request = request;
+ ctx->response = &response;
+
+ if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) {
+ gst_rtsp_message_dump (request);
+ }
+
+ gst_rtsp_message_parse_request (request, &method, &uristr, &version);
+
+ GST_INFO ("client %p: received a request %s %s %s", client,
+ gst_rtsp_method_as_text (method), uristr,
+ gst_rtsp_version_as_text (version));
+
+ /* we can only handle 1.0 requests */
+ if (version != GST_RTSP_VERSION_1_0 && version != GST_RTSP_VERSION_2_0)
+ goto not_supported;
+
+ ctx->method = method;
+
+ /* we always try to parse the url first */
+ if (strcmp (uristr, "*") == 0) {
+ /* special case where we have * as uri, keep uri = NULL */
+ } else if (gst_rtsp_url_parse (uristr, &uri) != GST_RTSP_OK) {
+ /* check if the uristr is an absolute path <=> scheme and host information
+ * is missing */
+ gchar *scheme;
+
+ scheme = g_uri_parse_scheme (uristr);
+ if (scheme == NULL && g_str_has_prefix (uristr, "/")) {
+ gchar *absolute_uristr = NULL;
+
+ GST_WARNING_OBJECT (client, "request doesn't contain absolute url");
+ if (priv->server_ip == NULL) {
+ GST_WARNING_OBJECT (client, "host information missing");
+ goto bad_request;
+ }
+
+ absolute_uristr =
+ g_strdup_printf ("rtsp://%s%s", priv->server_ip, uristr);
+
+ GST_DEBUG_OBJECT (client, "absolute url: %s", absolute_uristr);
+ if (gst_rtsp_url_parse (absolute_uristr, &uri) != GST_RTSP_OK) {
+ g_free (absolute_uristr);
+ goto bad_request;
+ }
+ g_free (absolute_uristr);
+ } else {
+ g_free (scheme);
+ goto bad_request;
+ }
+ }
+
+ /* get the session if there is any */
+ res = gst_rtsp_message_get_header (request, GST_RTSP_HDR_PIPELINED_REQUESTS,
+ &pipelined_request_id, 0);
+ if (res == GST_RTSP_OK) {
+ sessid = g_hash_table_lookup (client->priv->pipelined_requests,
+ pipelined_request_id);
+
+ if (!sessid)
+ res = GST_RTSP_ERROR;
+ }
+
+ if (res != GST_RTSP_OK)
+ res =
+ gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &sessid, 0);
+
+ if (res == GST_RTSP_OK) {
+ if (priv->session_pool == NULL)
+ goto no_pool;
+
+ /* we had a session in the request, find it again */
+ if (!(session = gst_rtsp_session_pool_find (priv->session_pool, sessid)))
+ goto session_not_found;
+
+ /* we add the session to the client list of watched sessions. When a session
+ * disappears because it times out, we will be notified. If all sessions are
+ * gone, we will close the connection */
+ client_watch_session (client, session);
+ }
+
+ /* sanitize the uri */
+ if (uri)
+ sanitize_uri (uri);
+ ctx->uri = uri;
+ ctx->session = session;
+
+ if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_URL))
+ goto not_authorized;
+
+ /* handle any 'Require' headers */
+ if (!check_request_requirements (ctx, &unsupported_reqs))
+ goto unsupported_requirement;
+
+ /* now see what is asked and dispatch to a dedicated handler */
+ switch (method) {
+ case GST_RTSP_OPTIONS:
+ priv->version = version;
- handle_play_request (client, ctx);
++ klass->handle_options_request (client, ctx, version);
+ break;
+ case GST_RTSP_DESCRIBE:
+ handle_describe_request (client, ctx);
+ break;
+ case GST_RTSP_SETUP:
+ handle_setup_request (client, ctx);
+ break;
+ case GST_RTSP_PLAY:
- handle_set_param_request (client, ctx);
++ klass->handle_play_request (client, ctx);
+ break;
+ case GST_RTSP_PAUSE:
+ handle_pause_request (client, ctx);
+ break;
+ case GST_RTSP_TEARDOWN:
+ handle_teardown_request (client, ctx);
+ break;
+ case GST_RTSP_SET_PARAMETER:
- handle_get_param_request (client, ctx);
++ klass->handle_set_param_request (client, ctx);
+ break;
+ case GST_RTSP_GET_PARAMETER:
++ klass->handle_get_param_request (client, ctx);
+ break;
+ case GST_RTSP_ANNOUNCE:
+ if (version >= GST_RTSP_VERSION_2_0)
+ goto invalid_command_for_version;
+ handle_announce_request (client, ctx);
+ break;
+ case GST_RTSP_RECORD:
+ if (version >= GST_RTSP_VERSION_2_0)
+ goto invalid_command_for_version;
+ handle_record_request (client, ctx);
+ break;
+ case GST_RTSP_REDIRECT:
+ goto not_implemented;
+ case GST_RTSP_INVALID:
+ default:
+ goto bad_request;
+ }
+
+ done:
+ if (ctx == &sctx)
+ gst_rtsp_context_pop_current (ctx);
+ if (session)
+ g_object_unref (session);
+ if (uri)
+ gst_rtsp_url_free (uri);
+ return;
+
+ /* ERRORS */
+ not_supported:
+ {
+ GST_ERROR ("client %p: version %d not supported", client, version);
+ send_generic_response (client, GST_RTSP_STS_RTSP_VERSION_NOT_SUPPORTED,
+ ctx);
+ goto done;
+ }
+ invalid_command_for_version:
+ {
+ GST_ERROR ("client %p: invalid command for version", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ goto done;
+ }
+ bad_request:
+ {
+ GST_ERROR ("client %p: bad request", client);
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ goto done;
+ }
+ no_pool:
+ {
+ GST_ERROR ("client %p: no pool configured", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ goto done;
+ }
+ session_not_found:
+ {
+ GST_ERROR ("client %p: session not found", client);
+ send_generic_response (client, GST_RTSP_STS_SESSION_NOT_FOUND, ctx);
+ goto done;
+ }
+ not_authorized:
+ {
+ GST_ERROR ("client %p: not allowed", client);
+ /* error reply is already sent */
+ goto done;
+ }
+ unsupported_requirement:
+ {
+ GST_ERROR ("client %p: Required option is not supported (%s)", client,
+ unsupported_reqs);
+ send_option_not_supported_response (client, ctx, unsupported_reqs);
+ g_free (unsupported_reqs);
+ goto done;
+ }
+ not_implemented:
+ {
+ GST_ERROR ("client %p: method %d not implemented", client, method);
+ send_generic_response (client, GST_RTSP_STS_NOT_IMPLEMENTED, ctx);
+ goto done;
+ }
+ }
+
+
+ static void
+ handle_response (GstRTSPClient * client, GstRTSPMessage * response)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPResult res;
+ GstRTSPSession *session = NULL;
+ GstRTSPContext sctx = { NULL }, *ctx;
+ gchar *sessid;
+
+ if (!(ctx = gst_rtsp_context_get_current ())) {
+ ctx = &sctx;
+ ctx->auth = priv->auth;
+ gst_rtsp_context_push_current (ctx);
+ }
+
+ ctx->conn = priv->connection;
+ ctx->client = client;
+ ctx->request = NULL;
+ ctx->uri = NULL;
+ ctx->method = GST_RTSP_INVALID;
+ ctx->response = response;
+
+ if (gst_debug_category_get_threshold (rtsp_client_debug) >= GST_LEVEL_LOG) {
+ gst_rtsp_message_dump (response);
+ }
+
+ GST_INFO ("client %p: received a response", client);
+
+ /* get the session if there is any */
+ res =
+ gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &sessid, 0);
+ if (res == GST_RTSP_OK) {
+ if (priv->session_pool == NULL)
+ goto no_pool;
+
+ /* we had a session in the request, find it again */
+ if (!(session = gst_rtsp_session_pool_find (priv->session_pool, sessid)))
+ goto session_not_found;
+
+ /* we add the session to the client list of watched sessions. When a session
+ * disappears because it times out, we will be notified. If all sessions are
+ * gone, we will close the connection */
+ client_watch_session (client, session);
+ }
+
+ ctx->session = session;
+
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_HANDLE_RESPONSE],
+ 0, ctx);
+
+ done:
+ if (ctx == &sctx)
+ gst_rtsp_context_pop_current (ctx);
+ if (session)
+ g_object_unref (session);
+ return;
+
+ no_pool:
+ {
+ GST_ERROR ("client %p: no pool configured", client);
+ goto done;
+ }
+ session_not_found:
+ {
+ GST_ERROR ("client %p: session not found", client);
+ goto done;
+ }
+ }
+
+ static void
+ handle_data (GstRTSPClient * client, GstRTSPMessage * message)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPResult res;
+ guint8 channel;
+ guint8 *data;
+ guint size;
+ GstBuffer *buffer;
+ GstRTSPStreamTransport *trans;
+
+ /* find the stream for this message */
+ res = gst_rtsp_message_parse_data (message, &channel);
+ if (res != GST_RTSP_OK)
+ return;
+
+ gst_rtsp_message_get_body (message, &data, &size);
+ if (size < 2)
+ goto invalid_length;
+
+ gst_rtsp_message_steal_body (message, &data, &size);
+
+ /* Strip trailing \0 (which GstRTSPConnection adds) */
+ --size;
+
+ buffer = gst_buffer_new_wrapped (data, size);
+
+ trans =
+ g_hash_table_lookup (priv->transports, GINT_TO_POINTER ((gint) channel));
+ if (trans) {
+ GSocketAddress *addr;
+
+ /* Only create the socket address once for the transport, we don't really
+ * want to do that for every single packet.
+ *
+ * The netaddress meta is later used by the RTP stack to know where
+ * packets came from and allows us to match it again to a stream transport
+ *
+ * In theory we could use the remote socket address of the RTSP connection
+ * here, but this would fail with a custom configure_client_transport()
+ * implementation.
+ */
+ if (!(addr =
+ g_object_get_data (G_OBJECT (trans), "rtsp-client.remote-addr"))) {
+ const GstRTSPTransport *tr;
+ GInetAddress *iaddr;
+
+ tr = gst_rtsp_stream_transport_get_transport (trans);
+ iaddr = g_inet_address_new_from_string (tr->destination);
+ if (iaddr) {
+ addr = g_inet_socket_address_new (iaddr, tr->client_port.min);
+ g_object_unref (iaddr);
+ g_object_set_data_full (G_OBJECT (trans), "rtsp-client.remote-addr",
+ addr, (GDestroyNotify) g_object_unref);
+ }
+ }
+
+ if (addr) {
+ gst_buffer_add_net_address_meta (buffer, addr);
+ }
+
+ /* dispatch to the stream based on the channel number */
+ GST_LOG_OBJECT (client, "%u bytes of data on channel %u", size, channel);
+ gst_rtsp_stream_transport_recv_data (trans, channel, buffer);
+ } else {
+ GST_DEBUG_OBJECT (client, "received %u bytes of data for "
+ "unknown channel %u", size, channel);
+ gst_buffer_unref (buffer);
+ }
+
+ return;
+
+ /* ERRORS */
+ invalid_length:
+ {
+ GST_DEBUG ("client %p: Short message received, ignoring", client);
+ return;
+ }
+ }
+
+ /**
+ * gst_rtsp_client_set_session_pool:
+ * @client: a #GstRTSPClient
+ * @pool: (transfer none) (nullable): a #GstRTSPSessionPool
+ *
+ * Set @pool as the sessionpool for @client which it will use to find
+ * or allocate sessions. the sessionpool is usually inherited from the server
+ * that created the client but can be overridden later.
+ */
+ void
+ gst_rtsp_client_set_session_pool (GstRTSPClient * client,
+ GstRTSPSessionPool * pool)
+ {
+ GstRTSPSessionPool *old;
+ GstRTSPClientPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+
+ if (pool)
+ g_object_ref (pool);
+
+ g_mutex_lock (&priv->lock);
+ old = priv->session_pool;
+ priv->session_pool = pool;
+
+ if (priv->session_removed_id) {
+ g_signal_handler_disconnect (old, priv->session_removed_id);
+ priv->session_removed_id = 0;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ /* FIXME, should remove all sessions from the old pool for this client */
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_client_get_session_pool:
+ * @client: a #GstRTSPClient
+ *
+ * Get the #GstRTSPSessionPool object that @client uses to manage its sessions.
+ *
+ * Returns: (transfer full) (nullable): a #GstRTSPSessionPool, unref after usage.
+ */
+ GstRTSPSessionPool *
+ gst_rtsp_client_get_session_pool (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPSessionPool *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);
+
+ priv = client->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->session_pool))
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_client_set_mount_points:
+ * @client: a #GstRTSPClient
+ * @mounts: (transfer none) (nullable): a #GstRTSPMountPoints
+ *
+ * Set @mounts as the mount points for @client which it will use to map urls
+ * to media streams. These mount points are usually inherited from the server that
+ * created the client but can be overriden later.
+ */
+ void
+ gst_rtsp_client_set_mount_points (GstRTSPClient * client,
+ GstRTSPMountPoints * mounts)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPMountPoints *old;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+
+ if (mounts)
+ g_object_ref (mounts);
+
+ g_mutex_lock (&priv->lock);
+ old = priv->mount_points;
+ priv->mount_points = mounts;
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_client_get_mount_points:
+ * @client: a #GstRTSPClient
+ *
+ * Get the #GstRTSPMountPoints object that @client uses to manage its sessions.
+ *
+ * Returns: (transfer full) (nullable): a #GstRTSPMountPoints, unref after usage.
+ */
+ GstRTSPMountPoints *
+ gst_rtsp_client_get_mount_points (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPMountPoints *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);
+
+ priv = client->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->mount_points))
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_client_set_content_length_limit:
+ * @client: a #GstRTSPClient
+ * @limit: Content-Length limit
+ *
+ * Configure @client to use the specified Content-Length limit.
+ *
+ * Define an appropriate request size limit and reject requests exceeding the
+ * limit with response status 413 Request Entity Too Large
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_client_set_content_length_limit (GstRTSPClient * client, guint limit)
+ {
+ GstRTSPClientPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+ g_mutex_lock (&priv->lock);
+ priv->content_length_limit = limit;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_client_get_content_length_limit:
+ * @client: a #GstRTSPClient
+ *
+ * Get the Content-Length limit of @client.
+ *
+ * Returns: the Content-Length limit.
+ *
+ * Since: 1.18
+ */
+ guint
+ gst_rtsp_client_get_content_length_limit (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv;
+ glong content_length_limit;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), -1);
+ priv = client->priv;
+
+ g_mutex_lock (&priv->lock);
+ content_length_limit = priv->content_length_limit;
+ g_mutex_unlock (&priv->lock);
+
+ return content_length_limit;
+ }
+
+ /**
+ * gst_rtsp_client_set_auth:
+ * @client: a #GstRTSPClient
+ * @auth: (transfer none) (nullable): a #GstRTSPAuth
+ *
+ * configure @auth to be used as the authentication manager of @client.
+ */
+ void
+ gst_rtsp_client_set_auth (GstRTSPClient * client, GstRTSPAuth * auth)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPAuth *old;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+
+ if (auth)
+ g_object_ref (auth);
+
+ g_mutex_lock (&priv->lock);
+ old = priv->auth;
+ priv->auth = auth;
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+
+ /**
+ * gst_rtsp_client_get_auth:
+ * @client: a #GstRTSPClient
+ *
+ * Get the #GstRTSPAuth used as the authentication manager of @client.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPAuth of @client.
+ * g_object_unref() after usage.
+ */
+ GstRTSPAuth *
+ gst_rtsp_client_get_auth (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPAuth *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);
+
+ priv = client->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->auth))
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_client_set_thread_pool:
+ * @client: a #GstRTSPClient
+ * @pool: (transfer none) (nullable): a #GstRTSPThreadPool
+ *
+ * configure @pool to be used as the thread pool of @client.
+ */
+ void
+ gst_rtsp_client_set_thread_pool (GstRTSPClient * client,
+ GstRTSPThreadPool * pool)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPThreadPool *old;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+
+ if (pool)
+ g_object_ref (pool);
+
+ g_mutex_lock (&priv->lock);
+ old = priv->thread_pool;
+ priv->thread_pool = pool;
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_client_get_thread_pool:
+ * @client: a #GstRTSPClient
+ *
+ * Get the #GstRTSPThreadPool used as the thread pool of @client.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPThreadPool of @client. g_object_unref() after
+ * usage.
+ */
+ GstRTSPThreadPool *
+ gst_rtsp_client_get_thread_pool (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv;
+ GstRTSPThreadPool *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);
+
+ priv = client->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->thread_pool))
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_client_set_connection:
+ * @client: a #GstRTSPClient
+ * @conn: (transfer full): a #GstRTSPConnection
+ *
+ * Set the #GstRTSPConnection of @client. This function takes ownership of
+ * @conn.
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_client_set_connection (GstRTSPClient * client,
+ GstRTSPConnection * conn)
+ {
+ GstRTSPClientPrivate *priv;
+ GSocket *read_socket;
+ GSocketAddress *address;
+ GstRTSPUrl *url;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), FALSE);
+ g_return_val_if_fail (conn != NULL, FALSE);
+
+ priv = client->priv;
+
+ gst_rtsp_connection_set_content_length_limit (conn,
+ priv->content_length_limit);
+ read_socket = gst_rtsp_connection_get_read_socket (conn);
+
+ if (!(address = g_socket_get_local_address (read_socket, &error)))
+ goto no_address;
+
+ g_free (priv->server_ip);
+ /* keep the original ip that the client connected to */
+ if (G_IS_INET_SOCKET_ADDRESS (address)) {
+ GInetAddress *iaddr;
+
+ iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address));
+
+ /* socket might be ipv6 but adress still ipv4 */
+ priv->is_ipv6 = g_inet_address_get_family (iaddr) == G_SOCKET_FAMILY_IPV6;
+ priv->server_ip = g_inet_address_to_string (iaddr);
+ g_object_unref (address);
+ } else {
+ priv->is_ipv6 = g_socket_get_family (read_socket) == G_SOCKET_FAMILY_IPV6;
+ priv->server_ip = g_strdup ("unknown");
++ g_object_unref (address);
+ }
+
+ GST_INFO ("client %p connected to server ip %s, ipv6 = %d", client,
+ priv->server_ip, priv->is_ipv6);
+
+ url = gst_rtsp_connection_get_url (conn);
+ GST_INFO ("added new client %p ip %s:%d", client, url->host, url->port);
+
+ priv->connection = conn;
+
+ return TRUE;
+
+ /* ERRORS */
+ no_address:
+ {
+ GST_ERROR ("could not get local address %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_client_get_connection:
+ * @client: a #GstRTSPClient
+ *
+ * Get the #GstRTSPConnection of @client.
+ *
+ * Returns: (transfer none) (nullable): the #GstRTSPConnection of @client.
+ * The connection object returned remains valid until the client is freed.
+ */
+ GstRTSPConnection *
+ gst_rtsp_client_get_connection (GstRTSPClient * client)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);
+
+ return client->priv->connection;
+ }
+
+ /**
+ * gst_rtsp_client_set_send_func:
+ * @client: a #GstRTSPClient
+ * @func: (scope notified): a #GstRTSPClientSendFunc
+ * @user_data: (closure): user data passed to @func
+ * @notify: (allow-none): called when @user_data is no longer in use
+ *
+ * Set @func as the callback that will be called when a new message needs to be
+ * sent to the client. @user_data is passed to @func and @notify is called when
+ * @user_data is no longer in use.
+ *
+ * By default, the client will send the messages on the #GstRTSPConnection that
+ * was configured with gst_rtsp_client_attach() was called.
+ *
+ * It is only allowed to set either a `send_func` or a `send_messages_func`
+ * but not both at the same time.
+ */
+ void
+ gst_rtsp_client_set_send_func (GstRTSPClient * client,
+ GstRTSPClientSendFunc func, gpointer user_data, GDestroyNotify notify)
+ {
+ GstRTSPClientPrivate *priv;
+ GDestroyNotify old_notify;
+ gpointer old_data;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+
+ g_mutex_lock (&priv->send_lock);
+ g_assert (func == NULL || priv->send_messages_func == NULL);
+ priv->send_func = func;
+ old_notify = priv->send_notify;
+ old_data = priv->send_data;
+ priv->send_notify = notify;
+ priv->send_data = user_data;
+ g_mutex_unlock (&priv->send_lock);
+
+ if (old_notify)
+ old_notify (old_data);
+ }
+
+ /**
+ * gst_rtsp_client_set_send_messages_func:
+ * @client: a #GstRTSPClient
+ * @func: (scope notified): a #GstRTSPClientSendMessagesFunc
+ * @user_data: (closure): user data passed to @func
+ * @notify: (allow-none): called when @user_data is no longer in use
+ *
+ * Set @func as the callback that will be called when new messages needs to be
+ * sent to the client. @user_data is passed to @func and @notify is called when
+ * @user_data is no longer in use.
+ *
+ * By default, the client will send the messages on the #GstRTSPConnection that
+ * was configured with gst_rtsp_client_attach() was called.
+ *
+ * It is only allowed to set either a `send_func` or a `send_messages_func`
+ * but not both at the same time.
+ *
+ * Since: 1.16
+ */
+ void
+ gst_rtsp_client_set_send_messages_func (GstRTSPClient * client,
+ GstRTSPClientSendMessagesFunc func, gpointer user_data,
+ GDestroyNotify notify)
+ {
+ GstRTSPClientPrivate *priv;
+ GDestroyNotify old_notify;
+ gpointer old_data;
+
+ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
+
+ priv = client->priv;
+
+ g_mutex_lock (&priv->send_lock);
+ g_assert (func == NULL || priv->send_func == NULL);
+ priv->send_messages_func = func;
+ old_notify = priv->send_messages_notify;
+ old_data = priv->send_messages_data;
+ priv->send_messages_notify = notify;
+ priv->send_messages_data = user_data;
+ g_mutex_unlock (&priv->send_lock);
+
+ if (old_notify)
+ old_notify (old_data);
+ }
+
+ /**
+ * gst_rtsp_client_handle_message:
+ * @client: a #GstRTSPClient
+ * @message: (transfer none): an #GstRTSPMessage
+ *
+ * Let the client handle @message.
+ *
+ * Returns: a #GstRTSPResult.
+ */
+ GstRTSPResult
+ gst_rtsp_client_handle_message (GstRTSPClient * client,
+ GstRTSPMessage * message)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), GST_RTSP_EINVAL);
+ g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
+
+ switch (message->type) {
+ case GST_RTSP_MESSAGE_REQUEST:
+ handle_request (client, message);
+ break;
+ case GST_RTSP_MESSAGE_RESPONSE:
+ handle_response (client, message);
+ break;
+ case GST_RTSP_MESSAGE_DATA:
+ handle_data (client, message);
+ break;
+ default:
+ break;
+ }
+ return GST_RTSP_OK;
+ }
+
+ /**
+ * gst_rtsp_client_send_message:
+ * @client: a #GstRTSPClient
+ * @session: (allow-none) (transfer none): a #GstRTSPSession to send
+ * the message to or %NULL
+ * @message: (transfer none): The #GstRTSPMessage to send
+ *
+ * Send a message message to the remote end. @message must be a
+ * #GST_RTSP_MESSAGE_REQUEST or a #GST_RTSP_MESSAGE_RESPONSE.
+ */
+ GstRTSPResult
+ gst_rtsp_client_send_message (GstRTSPClient * client, GstRTSPSession * session,
+ GstRTSPMessage * message)
+ {
+ GstRTSPContext sctx = { NULL }
+ , *ctx;
+ GstRTSPClientPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), GST_RTSP_EINVAL);
+ g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
+ g_return_val_if_fail (message->type == GST_RTSP_MESSAGE_REQUEST ||
+ message->type == GST_RTSP_MESSAGE_RESPONSE, GST_RTSP_EINVAL);
+
+ priv = client->priv;
+
+ if (!(ctx = gst_rtsp_context_get_current ())) {
+ ctx = &sctx;
+ ctx->auth = priv->auth;
+ gst_rtsp_context_push_current (ctx);
+ }
+
+ ctx->conn = priv->connection;
+ ctx->client = client;
+ ctx->session = session;
+
+ send_message (client, ctx, message, FALSE);
+
+ if (ctx == &sctx)
+ gst_rtsp_context_pop_current (ctx);
+
+ return GST_RTSP_OK;
+ }
+
+ /**
+ * gst_rtsp_client_get_stream_transport:
+ *
+ * This is useful when providing a send function through
+ * gst_rtsp_client_set_send_func() when doing RTSP over TCP:
+ * the send function must call gst_rtsp_stream_transport_message_sent ()
+ * on the appropriate transport when data has been received for streaming
+ * to continue.
+ *
+ * Returns: (transfer none) (nullable): the #GstRTSPStreamTransport associated with @channel.
+ *
+ * Since: 1.18
+ */
+ GstRTSPStreamTransport *
+ gst_rtsp_client_get_stream_transport (GstRTSPClient * self, guint8 channel)
+ {
+ return g_hash_table_lookup (self->priv->transports,
+ GINT_TO_POINTER ((gint) channel));
+ }
+
+ static gboolean
+ do_send_messages (GstRTSPClient * client, GstRTSPMessage * messages,
+ guint n_messages, gboolean close, gpointer user_data)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ guint id = 0;
+ GstRTSPResult ret;
+ guint i;
+
+ /* send the message */
+ if (close)
+ GST_INFO ("client %p: sending close message", client);
+
+ ret = gst_rtsp_watch_send_messages (priv->watch, messages, n_messages, &id);
+ if (ret != GST_RTSP_OK)
+ goto error;
+
+ for (i = 0; i < n_messages; i++) {
+ if (gst_rtsp_message_get_type (&messages[i]) == GST_RTSP_MESSAGE_DATA) {
+ guint8 channel = 0;
+ GstRTSPResult r;
+
+ /* We assume that all data messages in the list are for the
+ * same channel */
+ r = gst_rtsp_message_parse_data (&messages[i], &channel);
+ if (r != GST_RTSP_OK) {
+ ret = r;
+ goto error;
+ }
+
+ /* check if the message has been queued for transmission in watch */
+ if (id) {
+ /* store the seq number so we can wait until it has been sent */
+ GST_DEBUG_OBJECT (client, "wait for message %d, channel %d", id,
+ channel);
+ set_data_seq (client, channel, id);
+ } else {
+ GstRTSPStreamTransport *trans;
+
+ trans =
+ g_hash_table_lookup (priv->transports,
+ GINT_TO_POINTER ((gint) channel));
+ if (trans) {
+ GST_DEBUG_OBJECT (client, "emit 'message-sent' signal");
+ g_mutex_unlock (&priv->send_lock);
+ gst_rtsp_stream_transport_message_sent (trans);
+ g_mutex_lock (&priv->send_lock);
+ }
+ }
+ break;
+ }
+ }
+
+ return ret == GST_RTSP_OK;
+
+ /* ERRORS */
+ error:
+ {
+ GST_DEBUG_OBJECT (client, "got error %d", ret);
+ return FALSE;
+ }
+ }
+
+ static GstRTSPResult
+ message_received (GstRTSPWatch * watch, GstRTSPMessage * message,
+ gpointer user_data)
+ {
+ return gst_rtsp_client_handle_message (GST_RTSP_CLIENT (user_data), message);
+ }
+
+ static GstRTSPResult
+ message_sent (GstRTSPWatch * watch, guint cseq, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPStreamTransport *trans = NULL;
+ guint8 channel = 0;
+
+ g_mutex_lock (&priv->send_lock);
+
+ if (get_data_channel (client, cseq, &channel)) {
+ trans = g_hash_table_lookup (priv->transports, GINT_TO_POINTER (channel));
+ set_data_seq (client, channel, 0);
+ }
+ g_mutex_unlock (&priv->send_lock);
+
+ if (trans) {
+ GST_DEBUG_OBJECT (client, "emit 'message-sent' signal");
+ gst_rtsp_stream_transport_message_sent (trans);
+ }
+
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPResult
+ closed (GstRTSPWatch * watch, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+ GstRTSPClientPrivate *priv = client->priv;
+ const gchar *tunnelid;
+
+ GST_INFO ("client %p: connection closed", client);
+
+ if ((tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection))) {
+ g_mutex_lock (&tunnels_lock);
+ /* remove from tunnelids */
+ g_hash_table_remove (tunnels, tunnelid);
+ g_mutex_unlock (&tunnels_lock);
+ }
+
+ gst_rtsp_watch_set_flushing (watch, TRUE);
+ g_mutex_lock (&priv->watch_lock);
+ gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
+ gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
+ g_mutex_unlock (&priv->watch_lock);
+
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPResult
+ error (GstRTSPWatch * watch, GstRTSPResult result, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+ gchar *str;
+
+ str = gst_rtsp_strresult (result);
+ GST_INFO ("client %p: received an error %s", client, str);
+ g_free (str);
+
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPResult
+ error_full (GstRTSPWatch * watch, GstRTSPResult result,
+ GstRTSPMessage * message, guint id, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+ gchar *str;
+ GstRTSPContext sctx = { NULL }, *ctx;
+ GstRTSPClientPrivate *priv;
+ GstRTSPMessage response = { 0 };
+ priv = client->priv;
+
+ if (!(ctx = gst_rtsp_context_get_current ())) {
+ ctx = &sctx;
+ ctx->auth = priv->auth;
+ gst_rtsp_context_push_current (ctx);
+ }
+
+ ctx->conn = priv->connection;
+ ctx->client = client;
+ ctx->request = message;
+ ctx->method = GST_RTSP_INVALID;
+ ctx->response = &response;
+
+ /* only return error response if it is a request */
+ if (!message || message->type != GST_RTSP_MESSAGE_REQUEST)
+ goto done;
+
+ if (result == GST_RTSP_ENOMEM) {
+ send_generic_response (client, GST_RTSP_STS_REQUEST_ENTITY_TOO_LARGE, ctx);
+ goto done;
+ }
+ if (result == GST_RTSP_EPARSE) {
+ send_generic_response (client, GST_RTSP_STS_BAD_REQUEST, ctx);
+ goto done;
+ }
+
+ done:
+ if (ctx == &sctx)
+ gst_rtsp_context_pop_current (ctx);
+ str = gst_rtsp_strresult (result);
+ GST_INFO
+ ("client %p: error when handling message %p with id %d: %s",
+ client, message, id, str);
+ g_free (str);
+
+ return GST_RTSP_OK;
+ }
+
+ static gboolean
+ remember_tunnel (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ const gchar *tunnelid;
+
+ /* store client in the pending tunnels */
+ tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection);
+ if (tunnelid == NULL)
+ goto no_tunnelid;
+
+ GST_INFO ("client %p: inserting tunnel session %s", client, tunnelid);
+
+ /* we can't have two clients connecting with the same tunnelid */
+ g_mutex_lock (&tunnels_lock);
+ if (g_hash_table_lookup (tunnels, tunnelid))
+ goto tunnel_existed;
+
+ g_hash_table_insert (tunnels, g_strdup (tunnelid), g_object_ref (client));
+ g_mutex_unlock (&tunnels_lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_tunnelid:
+ {
+ GST_ERROR ("client %p: no tunnelid provided", client);
+ return FALSE;
+ }
+ tunnel_existed:
+ {
+ g_mutex_unlock (&tunnels_lock);
+ GST_ERROR ("client %p: tunnel session %s already existed", client,
+ tunnelid);
+ return FALSE;
+ }
+ }
+
+ static GstRTSPResult
+ tunnel_lost (GstRTSPWatch * watch, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+ GstRTSPClientPrivate *priv = client->priv;
+
+ GST_WARNING ("client %p: tunnel lost (connection %p)", client,
+ priv->connection);
+
+ /* ignore error, it'll only be a problem when the client does a POST again */
+ remember_tunnel (client);
+
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPStatusCode
+ handle_tunnel (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ GstRTSPClient *oclient;
+ GstRTSPClientPrivate *opriv;
+ const gchar *tunnelid;
+
+ tunnelid = gst_rtsp_connection_get_tunnelid (priv->connection);
+ if (tunnelid == NULL)
+ goto no_tunnelid;
+
+ /* check for previous tunnel */
+ g_mutex_lock (&tunnels_lock);
+ oclient = g_hash_table_lookup (tunnels, tunnelid);
+
+ if (oclient == NULL) {
+ /* no previous tunnel, remember tunnel */
+ g_hash_table_insert (tunnels, g_strdup (tunnelid), g_object_ref (client));
+ g_mutex_unlock (&tunnels_lock);
+
+ GST_INFO ("client %p: no previous tunnel found, remembering tunnel (%p)",
+ client, priv->connection);
+ } else {
+ /* merge both tunnels into the first client */
+ /* remove the old client from the table. ref before because removing it will
+ * remove the ref to it. */
+ g_object_ref (oclient);
+ g_hash_table_remove (tunnels, tunnelid);
+ g_mutex_unlock (&tunnels_lock);
+
+ opriv = oclient->priv;
+
+ g_mutex_lock (&opriv->watch_lock);
+ if (opriv->watch == NULL)
+ goto tunnel_closed;
+ if (opriv->tstate == priv->tstate)
+ goto tunnel_duplicate_id;
+
+ GST_INFO ("client %p: found previous tunnel %p (old %p, new %p)", client,
+ oclient, opriv->connection, priv->connection);
+
+ gst_rtsp_connection_do_tunnel (opriv->connection, priv->connection);
+ gst_rtsp_watch_reset (priv->watch);
+ gst_rtsp_watch_reset (opriv->watch);
+ g_mutex_unlock (&opriv->watch_lock);
+ g_object_unref (oclient);
+
+ /* the old client owns the tunnel now, the new one will be freed */
+ g_source_destroy ((GSource *) priv->watch);
+ priv->watch = NULL;
+ gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
+ gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
+ rtsp_ctrl_timeout_remove (client);
+ }
+
+ return GST_RTSP_STS_OK;
+
+ /* ERRORS */
+ no_tunnelid:
+ {
+ GST_ERROR ("client %p: no tunnelid provided", client);
+ return GST_RTSP_STS_SERVICE_UNAVAILABLE;
+ }
+ tunnel_closed:
+ {
+ GST_ERROR ("client %p: tunnel session %s was closed", client, tunnelid);
+ g_mutex_unlock (&opriv->watch_lock);
+ g_object_unref (oclient);
+ return GST_RTSP_STS_SERVICE_UNAVAILABLE;
+ }
+ tunnel_duplicate_id:
+ {
+ GST_ERROR ("client %p: tunnel session %s was duplicate", client, tunnelid);
+ g_mutex_unlock (&opriv->watch_lock);
+ g_object_unref (oclient);
+ return GST_RTSP_STS_BAD_REQUEST;
+ }
+ }
+
+ static GstRTSPStatusCode
+ tunnel_get (GstRTSPWatch * watch, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+
+ GST_INFO ("client %p: tunnel get (connection %p)", client,
+ client->priv->connection);
+
+ g_mutex_lock (&client->priv->lock);
+ client->priv->tstate = TUNNEL_STATE_GET;
+ g_mutex_unlock (&client->priv->lock);
+
+ return handle_tunnel (client);
+ }
+
+ static GstRTSPResult
+ tunnel_post (GstRTSPWatch * watch, gpointer user_data)
+ {
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+
+ GST_INFO ("client %p: tunnel post (connection %p)", client,
+ client->priv->connection);
+
+ g_mutex_lock (&client->priv->lock);
+ client->priv->tstate = TUNNEL_STATE_POST;
+ g_mutex_unlock (&client->priv->lock);
+
+ if (handle_tunnel (client) != GST_RTSP_STS_OK)
+ return GST_RTSP_ERROR;
+
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPResult
+ tunnel_http_response (GstRTSPWatch * watch, GstRTSPMessage * request,
+ GstRTSPMessage * response, gpointer user_data)
+ {
+ GstRTSPClientClass *klass;
+
+ GstRTSPClient *client = GST_RTSP_CLIENT (user_data);
+ klass = GST_RTSP_CLIENT_GET_CLASS (client);
+
+ if (klass->tunnel_http_response) {
+ klass->tunnel_http_response (client, request, response);
+ }
+
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPWatchFuncs watch_funcs = {
+ message_received,
+ message_sent,
+ closed,
+ error,
+ tunnel_get,
+ tunnel_post,
+ error_full,
+ tunnel_lost,
+ tunnel_http_response
+ };
+
+ static void
+ client_watch_notify (GstRTSPClient * client)
+ {
+ GstRTSPClientPrivate *priv = client->priv;
+ gboolean closed = TRUE;
+
+ GST_INFO ("client %p: watch destroyed", client);
+ priv->watch = NULL;
+ /* remove all sessions if the media says so and so drop the extra client ref */
+ gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
+ gst_rtsp_client_set_send_messages_func (client, NULL, NULL, NULL);
+ rtsp_ctrl_timeout_remove (client);
+ gst_rtsp_client_session_filter (client, cleanup_session, &closed);
+
+ if (closed)
+ g_signal_emit (client, gst_rtsp_client_signals[SIGNAL_CLOSED], 0, NULL);
+ g_object_unref (client);
+ }
+
+ /**
+ * gst_rtsp_client_attach:
+ * @client: a #GstRTSPClient
+ * @context: (allow-none): a #GMainContext
+ *
+ * Attaches @client to @context. When the mainloop for @context is run, the
+ * client will be dispatched. When @context is %NULL, the default context will be
+ * used).
+ *
+ * This function should be called when the client properties and urls are fully
+ * configured and the client is ready to start.
+ *
+ * Returns: the ID (greater than 0) for the source within the GMainContext.
+ */
+ guint
+ gst_rtsp_client_attach (GstRTSPClient * client, GMainContext * context)
+ {
+ GstRTSPClientPrivate *priv;
+ GSource *timer_src;
+ guint res;
+ GWeakRef *client_weak_ref = g_new (GWeakRef, 1);
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), 0);
+ priv = client->priv;
+ g_return_val_if_fail (priv->connection != NULL, 0);
+ g_return_val_if_fail (priv->watch == NULL, 0);
+ g_return_val_if_fail (priv->watch_context == NULL, 0);
+
+ /* make sure noone will free the context before the watch is destroyed */
+ priv->watch_context = g_main_context_ref (context);
+
+ /* create watch for the connection and attach */
+ priv->watch = gst_rtsp_watch_new (priv->connection, &watch_funcs,
+ g_object_ref (client), (GDestroyNotify) client_watch_notify);
+ gst_rtsp_client_set_send_func (client, NULL, NULL, NULL);
+ gst_rtsp_client_set_send_messages_func (client, do_send_messages, priv->watch,
+ (GDestroyNotify) gst_rtsp_watch_unref);
+
+ gst_rtsp_watch_set_send_backlog (priv->watch, 0, WATCH_BACKLOG_SIZE);
+
+ GST_INFO ("client %p: attaching to context %p", client, context);
+ res = gst_rtsp_watch_attach (priv->watch, context);
+
+ /* Setting up a timeout for the RTSP control channel until a session
+ * is up where it is handling timeouts. */
+ g_mutex_lock (&priv->lock);
+
+ /* remove old timeout if any */
+ rtsp_ctrl_timeout_remove_unlocked (client->priv);
+
+ timer_src = g_timeout_source_new_seconds (RTSP_CTRL_CB_INTERVAL);
+ g_weak_ref_init (client_weak_ref, client);
+ g_source_set_callback (timer_src, rtsp_ctrl_timeout_cb, client_weak_ref,
+ rtsp_ctrl_timeout_destroy_notify);
+ g_source_attach (timer_src, priv->watch_context);
+ priv->rtsp_ctrl_timeout = timer_src;
+ GST_DEBUG ("rtsp control setting up session timeout %p.",
+ priv->rtsp_ctrl_timeout);
+
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_client_session_filter:
+ * @client: a #GstRTSPClient
+ * @func: (scope call) (allow-none): a callback
+ * @user_data: user data passed to @func
+ *
+ * Call @func for each session managed by @client. The result value of @func
+ * determines what happens to the session. @func will be called with @client
+ * locked so no further actions on @client can be performed from @func.
+ *
+ * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be removed from
+ * @client.
+ *
+ * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @client.
+ *
+ * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @client but
+ * will also be added with an additional ref to the result #GList of this
+ * function..
+ *
+ * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each session.
+ *
+ * Returns: (element-type GstRTSPSession) (transfer full): a #GList with all
+ * sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each
+ * element in the #GList should be unreffed before the list is freed.
+ */
+ GList *
+ gst_rtsp_client_session_filter (GstRTSPClient * client,
+ GstRTSPClientSessionFilterFunc func, gpointer user_data)
+ {
+ GstRTSPClientPrivate *priv;
+ GList *result, *walk, *next;
+ GHashTable *visited;
+ guint cookie;
+
+ g_return_val_if_fail (GST_IS_RTSP_CLIENT (client), NULL);
+
+ priv = client->priv;
+
+ result = NULL;
+ if (func)
+ visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+
+ g_mutex_lock (&priv->lock);
+ restart:
+ cookie = priv->sessions_cookie;
+ for (walk = priv->sessions; walk; walk = next) {
+ GstRTSPSession *sess = walk->data;
+ GstRTSPFilterResult res;
+ gboolean changed;
+
+ next = g_list_next (walk);
+
+ if (func) {
+ /* only visit each session once */
+ if (g_hash_table_contains (visited, sess))
+ continue;
+
+ g_hash_table_add (visited, g_object_ref (sess));
+ g_mutex_unlock (&priv->lock);
+
+ res = func (client, sess, user_data);
+
+ g_mutex_lock (&priv->lock);
+ } else
+ res = GST_RTSP_FILTER_REF;
+
+ changed = (cookie != priv->sessions_cookie);
+
+ switch (res) {
+ case GST_RTSP_FILTER_REMOVE:
+ /* stop watching the session and pretend it went away, if the list was
+ * changed, we can't use the current list position, try to see if we
+ * still have the session */
+ client_unwatch_session (client, sess, changed ? NULL : walk);
+ cookie = priv->sessions_cookie;
+ break;
+ case GST_RTSP_FILTER_REF:
+ result = g_list_prepend (result, g_object_ref (sess));
+ break;
+ case GST_RTSP_FILTER_KEEP:
+ default:
+ break;
+ }
+ if (changed)
+ goto restart;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (func)
+ g_hash_table_unref (visited);
+
+ return result;
+ }
++
++/**
++ * gst_rtsp_client_set_watch_flushing:
++ * @client: a #GstRTSPClient
++ * @val: a boolean value
++ *
++ * sets watch flushing to @val on watch to accet/ignore new messages.
++ */
++void
++gst_rtsp_client_set_watch_flushing (GstRTSPClient * client, gboolean val)
++{
++ GstRTSPClientPrivate *priv = NULL;
++ g_return_if_fail (GST_IS_RTSP_CLIENT (client));
++
++ priv = gst_rtsp_client_get_instance_private (client);
++
++ /* make sure we unblock/block the backlog and accept/don't accept new messages on the watch */
++ if (priv->watch != NULL) {
++ GST_INFO ("Set watch flushing as %d", val);
++ gst_rtsp_watch_set_flushing (priv->watch, val);
++ }
++}
--- /dev/null
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ *
+ * 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 <gst/gst.h>
+ #include <gst/rtsp/gstrtspconnection.h>
+
+ #ifndef __GST_RTSP_CLIENT_H__
+ #define __GST_RTSP_CLIENT_H__
+
+ G_BEGIN_DECLS
+
+ typedef struct _GstRTSPClient GstRTSPClient;
+ typedef struct _GstRTSPClientClass GstRTSPClientClass;
+ typedef struct _GstRTSPClientPrivate GstRTSPClientPrivate;
+
+ #include "rtsp-server-prelude.h"
+ #include "rtsp-context.h"
+ #include "rtsp-mount-points.h"
+ #include "rtsp-sdp.h"
+ #include "rtsp-auth.h"
+
+ #define GST_TYPE_RTSP_CLIENT (gst_rtsp_client_get_type ())
+ #define GST_IS_RTSP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_CLIENT))
+ #define GST_IS_RTSP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_CLIENT))
+ #define GST_RTSP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClientClass))
+ #define GST_RTSP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_CLIENT, GstRTSPClient))
+ #define GST_RTSP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_CLIENT, GstRTSPClientClass))
+ #define GST_RTSP_CLIENT_CAST(obj) ((GstRTSPClient*)(obj))
+ #define GST_RTSP_CLIENT_CLASS_CAST(klass) ((GstRTSPClientClass*)(klass))
+
+ /**
+ * GstRTSPClientSendFunc:
+ * @client: a #GstRTSPClient
+ * @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 (*GstRTSPClientSendFunc) (GstRTSPClient *client,
+ GstRTSPMessage *message,
+ gboolean close,
+ gpointer user_data);
+
+ /**
+ * GstRTSPClientSendMessagesFunc:
+ * @client: a #GstRTSPClient
+ * @messages: #GstRTSPMessage
+ * @n_messages: number of messages
+ * @close: close the connection
+ * @user_data: user data when registering the callback
+ *
+ * This callback is called when @client wants to send @messages. When @close is
+ * %TRUE, the connection should be closed when the message has been sent.
+ *
+ * Returns: %TRUE on success.
+ *
+ * Since: 1.16
+ */
+ typedef gboolean (*GstRTSPClientSendMessagesFunc) (GstRTSPClient *client,
+ GstRTSPMessage *messages,
+ guint n_messages,
+ gboolean close,
+ gpointer user_data);
+
+ /**
+ * GstRTSPClient:
+ *
+ * The client object represents the connection and its state with a client.
+ */
+ struct _GstRTSPClient {
+ GObject parent;
+
+ /*< private >*/
+ GstRTSPClientPrivate *priv;
+ gpointer _gst_reserved[GST_PADDING];
+ };
+
+ /**
+ * GstRTSPClientClass:
+ * @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()
+ * @make_path_from_uri: called to create path from uri.
+ * @adjust_play_mode: called to give the application the possibility to adjust
+ * the range, seek flags, rate and rate-control. Since 1.18
+ * @adjust_play_response: called to give the implementation the possibility to
+ * adjust the response to a play request, for example if extra headers were
+ * parsed when #GstRTSPClientClass.adjust_play_mode was called. Since 1.18
+ * @tunnel_http_response: called when a response to the GET request is about to
+ * be sent for a tunneled connection. The response can be modified. Since: 1.4
+ *
+ * The client class structure.
+ */
+ struct _GstRTSPClientClass {
+ GObjectClass parent_class;
+
+ GstSDPMessage * (*create_sdp) (GstRTSPClient *client, GstRTSPMedia *media);
+ gboolean (*configure_client_media) (GstRTSPClient * client,
+ GstRTSPMedia * media, GstRTSPStream * stream,
+ GstRTSPContext * ctx);
+ gboolean (*configure_client_transport) (GstRTSPClient * client,
+ GstRTSPContext * ctx,
+ GstRTSPTransport * ct);
+ 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, GstRTSPVersion version);
++ 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);
+ GstRTSPStatusCode (*adjust_play_mode) (GstRTSPClient * client,
+ GstRTSPContext * context,
+ GstRTSPTimeRange ** range,
+ GstSeekFlags * flags,
+ gdouble * rate,
+ GstClockTime * trickmode_interval,
+ gboolean * enable_rate_control);
+ GstRTSPStatusCode (*adjust_play_response) (GstRTSPClient * client,
+ GstRTSPContext * context);
+
+ /* signals */
+ void (*closed) (GstRTSPClient *client);
+ void (*new_session) (GstRTSPClient *client, GstRTSPSession *session);
+ void (*options_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*describe_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*setup_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*play_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*pause_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*teardown_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*set_parameter_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*get_parameter_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*handle_response) (GstRTSPClient *client, GstRTSPContext *ctx);
+
+ void (*tunnel_http_response) (GstRTSPClient * client, GstRTSPMessage * request,
+ GstRTSPMessage * response);
+ void (*send_message) (GstRTSPClient * client, GstRTSPContext *ctx,
+ GstRTSPMessage * response);
+
+ gboolean (*handle_sdp) (GstRTSPClient *client, GstRTSPContext *ctx, GstRTSPMedia *media, GstSDPMessage *sdp);
+
+ void (*announce_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ void (*record_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ gchar* (*check_requirements) (GstRTSPClient *client, GstRTSPContext *ctx, gchar ** arr);
+
+ GstRTSPStatusCode (*pre_options_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_describe_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_setup_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_play_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_pause_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_teardown_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_set_parameter_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_get_parameter_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_announce_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+ GstRTSPStatusCode (*pre_record_request) (GstRTSPClient *client, GstRTSPContext *ctx);
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING_LARGE-18];
+ };
+
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_client_get_type (void);
+
+ GST_RTSP_SERVER_API
+ GstRTSPClient * gst_rtsp_client_new (void);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_session_pool (GstRTSPClient *client,
+ GstRTSPSessionPool *pool);
+
+ GST_RTSP_SERVER_API
+ GstRTSPSessionPool * gst_rtsp_client_get_session_pool (GstRTSPClient *client);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_mount_points (GstRTSPClient *client,
+ GstRTSPMountPoints *mounts);
+
+ GST_RTSP_SERVER_API
+ GstRTSPMountPoints * gst_rtsp_client_get_mount_points (GstRTSPClient *client);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_content_length_limit (GstRTSPClient *client, guint limit);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_client_get_content_length_limit (GstRTSPClient *client);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_auth (GstRTSPClient *client, GstRTSPAuth *auth);
+
+ GST_RTSP_SERVER_API
+ GstRTSPAuth * gst_rtsp_client_get_auth (GstRTSPClient *client);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_thread_pool (GstRTSPClient *client, GstRTSPThreadPool *pool);
+
+ GST_RTSP_SERVER_API
+ GstRTSPThreadPool * gst_rtsp_client_get_thread_pool (GstRTSPClient *client);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_client_set_connection (GstRTSPClient *client, GstRTSPConnection *conn);
+
+ GST_RTSP_SERVER_API
+ GstRTSPConnection * gst_rtsp_client_get_connection (GstRTSPClient *client);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_client_attach (GstRTSPClient *client,
+ GMainContext *context);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_close (GstRTSPClient * client);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_send_func (GstRTSPClient *client,
+ GstRTSPClientSendFunc func,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_client_set_send_messages_func (GstRTSPClient *client,
+ GstRTSPClientSendMessagesFunc func,
+ gpointer user_data,
+ GDestroyNotify notify);
+
+ GST_RTSP_SERVER_API
+ GstRTSPResult gst_rtsp_client_handle_message (GstRTSPClient *client,
+ GstRTSPMessage *message);
+
+ GST_RTSP_SERVER_API
+ GstRTSPResult gst_rtsp_client_send_message (GstRTSPClient * client,
+ GstRTSPSession *session,
+ GstRTSPMessage *message);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_client_set_watch_flushing (GstRTSPClient * client, gboolean val);
+ /**
+ * GstRTSPClientSessionFilterFunc:
+ * @client: a #GstRTSPClient object
+ * @sess: a #GstRTSPSession in @client
+ * @user_data: user data that has been given to gst_rtsp_client_session_filter()
+ *
+ * This function will be called by the gst_rtsp_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_client_session_filter().
+ *
+ * Returns: a #GstRTSPFilterResult.
+ */
+ typedef GstRTSPFilterResult (*GstRTSPClientSessionFilterFunc) (GstRTSPClient *client,
+ GstRTSPSession *sess,
+ gpointer user_data);
+
+ GST_RTSP_SERVER_API
+ GList * gst_rtsp_client_session_filter (GstRTSPClient *client,
+ GstRTSPClientSessionFilterFunc func,
+ gpointer user_data);
+
+ GST_RTSP_SERVER_API
+ GstRTSPStreamTransport * gst_rtsp_client_get_stream_transport (GstRTSPClient *client,
+ guint8 channel);
+
+
+ #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPClient, gst_object_unref)
+ #endif
+
+ G_END_DECLS
+
+ #endif /* __GST_RTSP_CLIENT_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
++ *
++ * 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
++ * @short_description: The media pipeline
++ * @see_also: #GstRTSPMediaFactory, #GstRTSPStream, #GstRTSPSession,
++ * #GstRTSPSessionMedia
++ *
++ * a #GstRTSPMedia contains the complete GStreamer pipeline to manage the
++ * streaming to the clients. The actual data transfer is done by the
++ * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia.
++ *
++ * The #GstRTSPMedia is usually created from a #GstRTSPMediaFactory when the
++ * client does a DESCRIBE or SETUP of a resource.
++ *
++ * A media is created with gst_rtsp_media_new() that takes the element that will
++ * provide the streaming elements. For each of the streams, a new #GstRTSPStream
++ * object needs to be made with the gst_rtsp_media_create_stream() which takes
++ * the payloader element and the source pad that produces the RTP stream.
++ *
++ * The pipeline of the media is set to PAUSED with gst_rtsp_media_prepare(). The
++ * prepare method will add rtpbin and sinks and sources to send and receive RTP
++ * and RTCP packets from the clients. Each stream srcpad is connected to an
++ * input into the internal rtpbin.
++ *
++ * It is also possible to dynamically create #GstRTSPStream objects during the
++ * prepare phase. With gst_rtsp_media_get_status() you can check the status of
++ * the prepare phase.
++ *
++ * After the media is prepared, it is ready for streaming. It will usually be
++ * managed in a session with gst_rtsp_session_manage_media(). See
++ * #GstRTSPSession and #GstRTSPSessionMedia.
++ *
++ * The state of the media can be controlled with gst_rtsp_media_set_state ().
++ * Seeking can be done with gst_rtsp_media_seek().
++ *
++ * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When
++ * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to
++ * cleanly shut down.
++ *
++ * With gst_rtsp_media_set_shared(), the media can be shared between multiple
++ * clients. With gst_rtsp_media_set_reusable() you can control if the pipeline
++ * can be prepared again after an unprepare.
++ *
++ * Last reviewed on 2013-07-11 (1.0.0)
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <string.h>
++#include <stdlib.h>
++
++#include <gst/app/gstappsrc.h>
++#include <gst/app/gstappsink.h>
++
++#include "rtsp-media-ext.h"
++
++#define GST_RTSP_MEDIA_EXT_GET_PRIVATE(obj) \
++ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_MEDIA_EXT, GstRTSPMediaExtPrivate))
++
++#define RTP_RETRANS_PORT 19120
++
++typedef struct _GstRTSPMediaExtRTPResender GstRTSPMediaExtRTPResender;
++
++struct _GstRTSPMediaExtRTPResender
++{
++ /* sinks used for sending and receiving RTP and RTCP over ipv4, they share
++ * sockets */
++ GstElement *udpsrc_v4;
++
++ /* for TCP transport */
++ GstElement *appsrc;
++ GstElement *funnel;
++ GstElement *resender;
++ GstElement *resend_sink;
++};
++
++struct _GstRTSPMediaExtPrivate
++{
++ GMutex lock;
++ GstRTSPMediaExtMode mode;
++
++ GstRTSPMediaExtRTPResender rtp_resender;
++ GstElement *fecenc;
++ gboolean is_joined;
++
++ /* pads on the rtpbin */
++ GstPad *send_src;
++
++ guint retransmit_port;
++ guint max_size_k;
++ guint max_size_p;
++ GstRTSPMediaExtLatency latency_mode;
++#ifdef FORCE_DROP
++ GstElement *identity;
++#endif
++};
++
++GST_DEBUG_CATEGORY_STATIC (rtsp_media_ext_debug);
++#define GST_CAT_DEFAULT rtsp_media_ext_debug
++
++static void gst_rtsp_media_ext_get_property (GObject * object, guint propid,
++ GValue * value, GParamSpec * pspec);
++static void gst_rtsp_media_ext_set_property (GObject * object, guint propid,
++ const GValue * value, GParamSpec * pspec);
++static void gst_rtsp_media_ext_finalize (GObject * obj);
++
++static void ext_preparing (GstRTSPMedia * media, GstRTSPStream * stream,
++ guint idx);
++static void ext_unpreparing (GstRTSPMedia * media, GstRTSPStream * stream,
++ guint idx);
++
++G_DEFINE_TYPE (GstRTSPMediaExt, gst_rtsp_media_ext, GST_TYPE_RTSP_MEDIA);
++
++static void
++gst_rtsp_media_ext_class_init (GstRTSPMediaExtClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstRTSPMediaClass *rtsp_media_class;
++
++ g_type_class_add_private (klass, sizeof (GstRTSPMediaExtPrivate));
++
++ gobject_class = G_OBJECT_CLASS (klass);
++ rtsp_media_class = GST_RTSP_MEDIA_CLASS (klass);
++
++ gobject_class->get_property = gst_rtsp_media_ext_get_property;
++ gobject_class->set_property = gst_rtsp_media_ext_set_property;
++ gobject_class->finalize = gst_rtsp_media_ext_finalize;
++
++ GST_DEBUG_CATEGORY_INIT (rtsp_media_ext_debug, "rtspmediaext", 0,
++ "GstRTSPMediaExt");
++
++ rtsp_media_class->preparing = ext_preparing;
++ rtsp_media_class->unpreparing = ext_unpreparing;
++}
++
++static void
++gst_rtsp_media_ext_init (GstRTSPMediaExt * media)
++{
++ GstRTSPMediaExtPrivate *priv = GST_RTSP_MEDIA_EXT_GET_PRIVATE (media);
++
++ media->priv = priv;
++ priv->is_joined = FALSE;
++ priv->mode = MEDIA_EXT_MODE_RESEND;
++ priv->retransmit_port = RTP_RETRANS_PORT;
++ priv->max_size_k = 10;
++ priv->max_size_p = 10;
++ priv->latency_mode = MEDIA_EXT_LATENCY_LOW;
++ memset (&priv->rtp_resender, 0x00, sizeof (GstRTSPMediaExtRTPResender));
++ g_mutex_init (&priv->lock);
++}
++
++static void
++gst_rtsp_media_ext_finalize (GObject * obj)
++{
++ GstRTSPMediaExtPrivate *priv;
++ GstRTSPMediaExt *media;
++
++ media = GST_RTSP_MEDIA_EXT (obj);
++ priv = media->priv;
++ g_mutex_clear (&priv->lock);
++
++ G_OBJECT_CLASS (gst_rtsp_media_ext_parent_class)->finalize (obj);
++}
++
++static void
++gst_rtsp_media_ext_get_property (GObject * object, guint propid, GValue * value,
++ GParamSpec * pspec)
++{
++}
++
++static void
++gst_rtsp_media_ext_set_property (GObject * object, guint propid,
++ const GValue * value, GParamSpec * pspec)
++{
++}
++
++GstRTSPMediaExt *
++gst_rtsp_media_ext_new (GstElement * element)
++{
++ GstRTSPMediaExt *result;
++
++ g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
++
++ result = g_object_new (GST_TYPE_RTSP_MEDIA_EXT, "element", element, NULL);
++
++ return result;
++}
++
++static gint in_idle_probe = FALSE;
++
++static gboolean
++alloc_ports (GstRTSPMediaExt * media)
++{
++ GstStateChangeReturn ret;
++ GstElement *udpsrc;
++ GstElement *udpsink;
++
++ gint tmp_feedback_rtcp;
++ gint feedback_rtcpport;
++
++ GInetAddress *inetaddr = NULL;
++ GSocketAddress *feedback_rtcp_sockaddr = NULL;
++ GSocket *feedback_rtp_socket;
++ GSocketFamily family = G_SOCKET_FAMILY_IPV4;
++ const gchar *sink_socket = "socket";
++ gchar *resend_uri = NULL;
++
++ GstRTSPMediaExtPrivate *priv;
++ priv = media->priv;
++
++ g_return_val_if_fail (priv != NULL, GST_PAD_PROBE_REMOVE);
++
++ udpsrc = NULL;
++ udpsink = NULL;
++
++ /* Start with random port */
++ tmp_feedback_rtcp = priv->retransmit_port + 1;
++
++ feedback_rtp_socket =
++ g_socket_new (family, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP,
++ NULL);
++
++ if (!feedback_rtp_socket)
++ goto no_udp_protocol;
++
++ inetaddr = g_inet_address_new_any (family);
++
++ feedback_rtcp_sockaddr =
++ g_inet_socket_address_new (inetaddr, tmp_feedback_rtcp);
++
++ g_object_unref (inetaddr);
++ inetaddr = NULL;
++
++ if (!g_socket_bind (feedback_rtp_socket, feedback_rtcp_sockaddr, FALSE, NULL)) {
++ g_object_unref (feedback_rtcp_sockaddr);
++ goto port_error;
++ }
++ g_object_unref (feedback_rtcp_sockaddr);
++
++ udpsrc = gst_element_factory_make ("udpsrc", NULL);
++
++ if (udpsrc == NULL)
++ goto no_udp_protocol;
++
++ g_object_set (G_OBJECT (udpsrc), "socket", feedback_rtp_socket, NULL);
++
++ ret = gst_element_set_state (udpsrc, GST_STATE_READY);
++ if (ret == GST_STATE_CHANGE_FAILURE)
++ goto element_error;
++
++ /* all fine, do port check */
++ g_object_get (G_OBJECT (udpsrc), "port", &feedback_rtcpport, NULL);
++
++ /* this should not happen... */
++ if (feedback_rtcpport != tmp_feedback_rtcp)
++ goto port_error;
++
++ resend_uri = g_strdup_printf ("udp://localhost:%d", priv->retransmit_port);
++ if (resend_uri) {
++ udpsink = gst_element_make_from_uri (GST_URI_SINK, resend_uri, NULL, NULL);
++ g_free (resend_uri);
++ }
++
++ if (!udpsink)
++ goto no_udp_protocol;
++
++ g_object_set (G_OBJECT (udpsink), "close-socket", FALSE, NULL);
++ g_object_set (G_OBJECT (udpsink), sink_socket, feedback_rtp_socket, NULL);
++ g_object_set (G_OBJECT (udpsink), "sync", FALSE, NULL);
++ g_object_set (G_OBJECT (udpsink), "async", FALSE, NULL);
++ g_object_set (G_OBJECT (udpsink), "send-duplicates", FALSE, NULL);
++ g_object_set (G_OBJECT (udpsink), "auto-multicast", FALSE, NULL);
++ g_object_set (G_OBJECT (udpsink), "loop", FALSE, NULL);
++
++ priv->rtp_resender.resend_sink = udpsink;
++ priv->rtp_resender.udpsrc_v4 = udpsrc;
++
++ return TRUE;
++
++ /* ERRORS */
++no_udp_protocol:
++ {
++ goto cleanup;
++ }
++port_error:
++ {
++ goto cleanup;
++ }
++element_error:
++ {
++ goto cleanup;
++ }
++cleanup:
++ {
++ if (udpsrc) {
++ gst_element_set_state (udpsrc, GST_STATE_NULL);
++ gst_object_unref (udpsrc);
++ }
++ if (udpsink) {
++ gst_element_set_state (udpsink, GST_STATE_NULL);
++ gst_object_unref (udpsink);
++ }
++ if (inetaddr)
++ g_object_unref (inetaddr);
++ return FALSE;
++ }
++}
++
++static GstPadProbeReturn
++pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
++{
++ GstPad *sinkpad, *resend_pad, *fecpad;
++ GstRTSPMediaExt *media = NULL;
++ GstRTSPMediaExtPrivate *priv;
++
++ if (!g_atomic_int_compare_and_exchange (&in_idle_probe, FALSE, TRUE))
++ return GST_PAD_PROBE_OK;
++
++ media = (GstRTSPMediaExt *) user_data;
++
++ priv = media->priv;
++
++ g_return_val_if_fail (priv != NULL, GST_PAD_PROBE_REMOVE);
++
++ sinkpad = gst_pad_get_peer (priv->send_src);
++ gst_pad_unlink (priv->send_src, sinkpad);
++
++ if (priv->mode & MEDIA_EXT_MODE_RESEND) {
++ GST_INFO_OBJECT (media, "joining resender");
++ resend_pad =
++ gst_element_get_static_pad (priv->rtp_resender.resender, "rtp_sink");
++ gst_pad_link (priv->send_src, resend_pad);
++ gst_object_unref (resend_pad);
++
++#ifdef FORCE_DROP
++ {
++ GstPad *identity_src, *identity_sink;
++ identity_src = gst_element_get_static_pad (priv->identity, "src");
++ identity_sink = gst_element_get_static_pad (priv->identity, "sink");
++ resend_pad =
++ gst_element_get_static_pad (priv->rtp_resender.resender, "send_src");
++ gst_pad_link (resend_pad, identity_sink);
++ gst_pad_link (identity_src, sinkpad);
++ gst_object_unref (identity_sink);
++ gst_object_unref (identity_src);
++ }
++#else
++ resend_pad =
++ gst_element_get_static_pad (priv->rtp_resender.resender, "send_src");
++ gst_pad_link (resend_pad, sinkpad);
++#endif
++ gst_object_unref (resend_pad);
++ } else if (priv->mode & MEDIA_EXT_MODE_FEC) {
++ GST_INFO_OBJECT (media, "joining fec encoder");
++ fecpad = gst_element_get_static_pad (priv->fecenc, "sink");
++ gst_pad_link (priv->send_src, fecpad);
++ gst_object_unref (fecpad);
++
++#ifdef FORCE_DROP
++ {
++ GstPad *identity_src, *identity_sink;
++ identity_src = gst_element_get_static_pad (priv->identity, "src");
++ identity_sink = gst_element_get_static_pad (priv->identity, "sink");
++
++ fecpad = gst_element_get_static_pad (priv->fecenc, "src");
++
++ gst_pad_link (fecpad, identity_sink);
++ gst_pad_link (identity_src, sinkpad);
++ gst_object_unref (identity_sink);
++ gst_object_unref (identity_src);
++ }
++#else
++ fecpad = gst_element_get_static_pad (priv->fecenc, "src");
++ gst_pad_link (fecpad, sinkpad);
++#endif
++ gst_object_unref (fecpad);
++ }
++
++ gst_object_unref (sinkpad);
++
++ return GST_PAD_PROBE_REMOVE;
++}
++
++static gboolean
++gst_rtsp_media_ext_join_extended_plugin (GstRTSPMediaExt * media, GstBin * bin,
++ GstElement * rtpbin, GstState state, guint idx)
++{
++ GstRTSPMediaExtPrivate *priv;
++ gchar *name;
++ GstPad *pad, *sinkpad, *selpad;
++ GstPad *resenderpad;
++
++ g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
++ g_return_val_if_fail (GST_IS_ELEMENT (rtpbin), FALSE);
++
++ priv = media->priv;
++ g_return_val_if_fail (priv != NULL, FALSE);
++
++ g_mutex_lock (&priv->lock);
++ if (priv->is_joined)
++ goto was_joined;
++
++ GST_INFO ("media %p joining rtp resender %u", media, idx);
++
++ /* get pads from the RTP session element for sending and receiving
++ * RTP/RTCP*/
++ name = g_strdup_printf ("send_rtp_src_%u", idx);
++ priv->send_src = gst_element_get_static_pad (rtpbin, name);
++ g_free (name);
++
++ /* make resender for RTP and link to stream */
++ priv->rtp_resender.resender = gst_element_factory_make ("rtpresender", NULL);
++ gst_bin_add (bin, priv->rtp_resender.resender);
++
++ gst_element_sync_state_with_parent (priv->rtp_resender.resender);
++
++ if (!alloc_ports (media))
++ goto no_ports;
++
++ /* For the sender we create this bit of pipeline for both
++ * RTP and RTCP. Sync and preroll are enabled on udpsink so
++ * we need to add a queue before appsink to make the pipeline
++ * not block. For the TCP case, we want to pump data to the
++ * client as fast as possible anyway.
++ *
++ * .--------. .-----. .---------.
++ * | rtpbin | | tee | | udpsink |
++ * | send->sink src->sink |
++ * '--------' | | '---------'
++ * | | .---------. .---------.
++ * | | | queue | | appsink |
++ * | src->sink src->sink |
++ * '-----' '---------' '---------'
++ *
++ * When only UDP is allowed, we skip the tee, queue and appsink and link the
++ * udpsink directly to the session.
++ */
++ /* add udpsink */
++ gst_bin_add (bin, priv->rtp_resender.resend_sink);
++ sinkpad = gst_element_get_static_pad (priv->rtp_resender.resend_sink, "sink");
++ resenderpad =
++ gst_element_get_static_pad (priv->rtp_resender.resender, "resend_src");
++
++ gst_pad_link (resenderpad, sinkpad);
++ gst_object_unref (resenderpad);
++ gst_object_unref (sinkpad);
++
++ /* For the receiver we create this bit of pipeline for both
++ * RTP and RTCP. We receive RTP/RTCP on appsrc and udpsrc
++ * and it is all funneled into the rtpbin receive pad.
++ *
++ * .--------. .--------. .--------.
++ * | udpsrc | | funnel | | rtpbin |
++ * | src->sink src->sink |
++ * '--------' | | '--------'
++ * .--------. | |
++ * | appsrc | | |
++ * | src->sink |
++ * '--------' '--------'
++ */
++ /* make funnel for the RTP/RTCP receivers */
++ priv->rtp_resender.funnel = gst_element_factory_make ("funnel", NULL);
++ gst_bin_add (bin, priv->rtp_resender.funnel);
++
++ resenderpad =
++ gst_element_get_static_pad (priv->rtp_resender.resender, "rtcp_sink");
++ pad = gst_element_get_static_pad (priv->rtp_resender.funnel, "src");
++ gst_pad_link (pad, resenderpad);
++ gst_object_unref (resenderpad);
++ gst_object_unref (pad);
++
++ if (priv->rtp_resender.udpsrc_v4) {
++ /* we set and keep these to playing so that they don't cause NO_PREROLL return
++ * values */
++ gst_element_set_state (priv->rtp_resender.udpsrc_v4, GST_STATE_PLAYING);
++ gst_element_set_locked_state (priv->rtp_resender.udpsrc_v4, TRUE);
++ /* add udpsrc */
++ gst_bin_add (bin, priv->rtp_resender.udpsrc_v4);
++
++ /* and link to the funnel v4 */
++ selpad = gst_element_get_request_pad (priv->rtp_resender.funnel, "sink_%u");
++ pad = gst_element_get_static_pad (priv->rtp_resender.udpsrc_v4, "src");
++ gst_pad_link (pad, selpad);
++ gst_object_unref (pad);
++ gst_object_unref (selpad);
++ }
++
++ /* make and add appsrc */
++ priv->rtp_resender.appsrc = gst_element_factory_make ("appsrc", NULL);
++ gst_bin_add (bin, priv->rtp_resender.appsrc);
++ /* and link to the funnel */
++ selpad = gst_element_get_request_pad (priv->rtp_resender.funnel, "sink_%u");
++ pad = gst_element_get_static_pad (priv->rtp_resender.appsrc, "src");
++ gst_pad_link (pad, selpad);
++ gst_object_unref (pad);
++ gst_object_unref (selpad);
++
++ /* check if we need to set to a special state */
++ if (state != GST_STATE_NULL) {
++ if (priv->rtp_resender.resend_sink)
++ gst_element_set_state (priv->rtp_resender.resend_sink, state);
++ if (priv->rtp_resender.funnel)
++ gst_element_set_state (priv->rtp_resender.funnel, state);
++ if (priv->rtp_resender.appsrc)
++ gst_element_set_state (priv->rtp_resender.appsrc, state);
++ }
++
++ /* make alfec encoder for RTP and link to stream */
++ priv->fecenc = gst_element_factory_make ("alfecencoder", NULL);
++ g_object_set (G_OBJECT (priv->fecenc), "max-size-k", priv->max_size_k, NULL);
++ g_object_set (G_OBJECT (priv->fecenc), "max-size-p", priv->max_size_p, NULL);
++ GST_DEBUG ("k:%d, p:%d", priv->max_size_k, priv->max_size_p);
++ g_object_set (G_OBJECT (priv->fecenc), "next-k", priv->max_size_k, NULL);
++ g_object_set (G_OBJECT (priv->fecenc), "next-p", priv->max_size_p, NULL);
++ g_object_set (G_OBJECT (priv->fecenc), "symbol-length", 1500, NULL);
++ gst_bin_add (bin, priv->fecenc);
++
++ gst_element_sync_state_with_parent (priv->fecenc);
++
++#ifdef FORCE_DROP
++ priv->identity = gst_element_factory_make ("identity", NULL);
++ g_object_set (G_OBJECT (priv->identity), "drop-probability", 0.05, NULL);
++ gst_bin_add (bin, priv->identity);
++
++ gst_element_sync_state_with_parent(priv->identity);
++#endif
++
++ in_idle_probe = FALSE;
++ gst_pad_add_probe (priv->send_src, GST_PAD_PROBE_TYPE_IDLE, pad_probe_cb,
++ media, NULL);
++
++ priv->is_joined = TRUE;
++ g_mutex_unlock (&priv->lock);
++
++ return TRUE;
++
++ /* ERRORS */
++was_joined:
++ {
++ g_mutex_unlock (&priv->lock);
++ return TRUE;
++ }
++no_ports:
++ {
++ g_mutex_unlock (&priv->lock);
++ GST_WARNING ("failed to allocate ports %u", idx);
++ return FALSE;
++ }
++}
++
++
++static void
++ext_preparing (GstRTSPMedia * media, GstRTSPStream * stream, guint idx)
++{
++ gboolean ret = FALSE;
++ GstElement *rtpbin = NULL;
++ GstElement *pipeline = NULL;
++ GstRTSPMediaExt *_media = GST_RTSP_MEDIA_EXT (media);
++ GstRTSPMediaExtPrivate *priv;
++
++ priv = _media->priv;
++ g_return_if_fail (priv != NULL);
++
++ pipeline = gst_rtsp_media_get_pipeline (media);
++ rtpbin = gst_rtsp_media_get_rtpbin (media);
++
++ ret =
++ gst_rtsp_media_ext_join_extended_plugin (_media, GST_BIN (pipeline),
++ rtpbin, GST_STATE_NULL, idx);
++ if (!ret)
++ GST_ERROR_OBJECT (_media, "Fatal error to join resender");
++
++ g_object_unref (pipeline);
++ g_object_unref (rtpbin);
++
++ return;
++}
++
++static gboolean
++gst_rtsp_media_ext_leave_extended_plugin (GstRTSPMediaExt * media, GstBin * bin,
++ GstElement * rtpbin)
++{
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
++ g_return_val_if_fail (GST_IS_ELEMENT (rtpbin), FALSE);
++
++ priv = media->priv;
++ g_return_val_if_fail (priv != NULL, FALSE);
++
++ g_mutex_lock (&priv->lock);
++ if (!priv->is_joined)
++ goto was_not_joined;
++
++ GST_INFO ("media %p leaving rtp resender", media);
++
++ if (priv->rtp_resender.resend_sink)
++ gst_element_set_state (priv->rtp_resender.resend_sink, GST_STATE_NULL);
++ if (priv->rtp_resender.funnel)
++ gst_element_set_state (priv->rtp_resender.funnel, GST_STATE_NULL);
++ if (priv->rtp_resender.appsrc)
++ gst_element_set_state (priv->rtp_resender.appsrc, GST_STATE_NULL);
++
++ if (priv->rtp_resender.udpsrc_v4) {
++ /* and set udpsrc to NULL now before removing */
++ gst_element_set_locked_state (priv->rtp_resender.udpsrc_v4, FALSE);
++ gst_element_set_state (priv->rtp_resender.udpsrc_v4, GST_STATE_NULL);
++ /* removing them should also nicely release the request
++ * pads when they finalize */
++ gst_bin_remove (bin, priv->rtp_resender.udpsrc_v4);
++ }
++
++ if (priv->rtp_resender.resend_sink)
++ gst_bin_remove (bin, priv->rtp_resender.resend_sink);
++ if (priv->rtp_resender.appsrc)
++ gst_bin_remove (bin, priv->rtp_resender.appsrc);
++ if (priv->rtp_resender.funnel)
++ gst_bin_remove (bin, priv->rtp_resender.funnel);
++
++ priv->rtp_resender.udpsrc_v4 = NULL;
++ priv->rtp_resender.resend_sink = NULL;
++ priv->rtp_resender.appsrc = NULL;
++ priv->rtp_resender.funnel = NULL;
++
++ GST_INFO ("media %p leaving fec encoder", media);
++
++ if (priv->fecenc) {
++ gst_element_set_state (priv->fecenc, GST_STATE_NULL);
++ priv->fecenc = NULL;
++ }
++
++ gst_object_unref (priv->send_src);
++ priv->send_src = NULL;
++ priv->is_joined = FALSE;
++ g_mutex_unlock (&priv->lock);
++
++ return TRUE;
++
++was_not_joined:
++ {
++ g_mutex_unlock (&priv->lock);
++ return TRUE;
++ }
++}
++
++
++static void
++ext_unpreparing (GstRTSPMedia * media, GstRTSPStream * stream, guint idx)
++{
++ gboolean ret = FALSE;
++ GstElement *rtpbin = NULL;
++ GstElement *pipeline = NULL;
++ GstRTSPMediaExt *_media = GST_RTSP_MEDIA_EXT (media);
++ GstRTSPMediaExtPrivate *priv;
++
++ priv = _media->priv;
++ g_return_if_fail (priv != NULL);
++
++ pipeline = gst_rtsp_media_get_pipeline (media);
++ rtpbin = gst_rtsp_media_get_rtpbin (media);
++
++ ret =
++ gst_rtsp_media_ext_leave_extended_plugin (_media, GST_BIN (pipeline),
++ rtpbin);
++
++ if (!ret)
++ GST_ERROR_OBJECT (_media, "Fatal error to leave resender");
++
++ g_object_unref (pipeline);
++ g_object_unref (rtpbin);
++
++ return;
++}
++
++guint
++gst_rtsp_media_ext_get_resent_packets (GstRTSPMediaExt * media)
++{
++ guint resent_packets = 0;
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_val_if_fail (GST_IS_RTSP_MEDIA_EXT (media), 0);
++
++ priv = media->priv;
++ g_return_val_if_fail (priv != NULL, 0);
++
++ g_object_get (G_OBJECT (priv->rtp_resender.resender), "rtp-packets-resend",
++ &resent_packets, NULL);
++
++ return resent_packets;
++}
++
++void
++gst_rtsp_media_ext_set_extended_mode (GstRTSPMediaExt * media,
++ GstRTSPMediaExtMode mode)
++{
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_if_fail (GST_IS_RTSP_MEDIA_EXT (media));
++
++ priv = media->priv;
++ g_return_if_fail (priv != NULL);
++
++ priv->mode = mode;
++}
++
++void
++gst_rtsp_media_ext_set_retrans_port (GstRTSPMediaExt * media, guint port)
++{
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_if_fail (GST_IS_RTSP_MEDIA_EXT (media));
++
++ priv = media->priv;
++ g_return_if_fail (priv != NULL);
++
++ priv->retransmit_port = port;
++}
++
++void
++gst_rtsp_media_ext_set_fec_value (GstRTSPMediaExt * media, guint max_k,
++ guint max_p)
++{
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_if_fail (GST_IS_RTSP_MEDIA_EXT (media));
++
++ priv = media->priv;
++ g_return_if_fail (priv != NULL);
++
++ priv->max_size_k = max_k;
++ priv->max_size_p = max_p;
++}
++
++void
++gst_rtsp_media_ext_set_latency_mode (GstRTSPMediaExt * media,
++ GstRTSPMediaExtLatency latency)
++{
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_if_fail (GST_IS_RTSP_MEDIA_EXT (media));
++
++ priv = media->priv;
++ g_return_if_fail (priv != NULL);
++
++ priv->latency_mode = latency;
++}
++
++void
++gst_rtsp_media_ext_set_next_param (GstRTSPMediaExt * media, gint32 next_k,
++ gint32 next_p)
++{
++ GstRTSPMediaExtPrivate *priv;
++
++ g_return_if_fail (GST_IS_RTSP_MEDIA_EXT (media));
++
++ priv = media->priv;
++ g_return_if_fail (priv != NULL);
++
++ g_object_set (G_OBJECT (priv->fecenc), "next-k", next_k, NULL);
++ g_object_set (G_OBJECT (priv->fecenc), "next-p", next_p, NULL);
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
++ *
++ * 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 <gst/gst.h>
++#include <gst/rtsp/gstrtsprange.h>
++#include <gst/rtsp/gstrtspurl.h>
++#include <gst/net/gstnet.h>
++
++#ifndef __GST_RTSP_MEDIA_EXT_H__
++#define __GST_RTSP_MEDIA_EXT_H__
++
++G_BEGIN_DECLS
++
++/* types for the media */
++#define GST_TYPE_RTSP_MEDIA_EXT (gst_rtsp_media_ext_get_type ())
++#define GST_IS_RTSP_MEDIA_EXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA_EXT))
++#define GST_IS_RTSP_MEDIA_EXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA_EXT))
++#define GST_RTSP_MEDIA_EXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA_EXT, GstRTSPMediaExtClass))
++#define GST_RTSP_MEDIA_EXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA_EXT, GstRTSPMediaExt))
++#define GST_RTSP_MEDIA_EXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA_EXT, GstRTSPMediaExtClass))
++#define GST_RTSP_MEDIA_EXT_CAST(obj) ((GstRTSPMediaExt*)(obj))
++#define GST_RTSP_MEDIA_EXT_CLASS_CAST(klass) ((GstRTSPMediaExtClass*)(klass))
++
++typedef struct _GstRTSPMediaExt GstRTSPMediaExt;
++typedef struct _GstRTSPMediaExtClass GstRTSPMediaExtClass;
++typedef struct _GstRTSPMediaExtPrivate GstRTSPMediaExtPrivate;
++
++#include "rtsp-stream.h"
++
++typedef enum {
++ MEDIA_EXT_MODE_NONE = (0 << 0),
++ MEDIA_EXT_MODE_RESEND = (1 << 0),
++ MEDIA_EXT_MODE_FEC = (1 << 1)
++} GstRTSPMediaExtMode;
++
++typedef enum {
++ MEDIA_EXT_LATENCY_NONE,
++ MEDIA_EXT_LATENCY_LOW,
++ MEDIA_EXT_LATENCY_MID,
++ MEDIA_EXT_LATENCY_HIGH
++} GstRTSPMediaExtLatency;
++
++/**
++ * GstRTSPMedia:
++ *
++ * A class that contains the GStreamer element along with a list of
++ * #GstRTSPStream objects that can produce data.
++ *
++ * This object is usually created from a #GstRTSPMediaFactory.
++ */
++struct _GstRTSPMediaExt {
++ GstRTSPMedia parent;
++
++ /*< private >*/
++ GstRTSPMediaExtPrivate *priv;
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++/**
++ * GstRTSPMediaClass:
++ * @handle_message: handle a message
++ * @prepare: the default implementation adds all elements and sets the
++ * pipeline's state to GST_STATE_PAUSED (or GST_STATE_PLAYING
++ * in case of NO_PREROLL elements).
++ * @unprepare: the default implementation sets the pipeline's state
++ * to GST_STATE_NULL and removes all elements.
++ * @suspend: the default implementation sets the pipeline's state to
++ * GST_STATE_NULL GST_STATE_PAUSED depending on the selected
++ * suspend mode.
++ * @unsuspend: the default implementation reverts the suspend operation.
++ * The pipeline will be prerolled again if it's state was
++ * set to GST_STATE_NULL in suspend.
++ * @convert_range: convert a range to the given unit
++ * @query_position: query the current position in the pipeline
++ * @query_stop: query when playback will stop
++ *
++ * The RTSP media class
++ */
++struct _GstRTSPMediaExtClass {
++ GstRTSPMediaClass parent_class;
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING_LARGE];
++};
++
++GType gst_rtsp_media_ext_get_type (void);
++
++/* creating the media */
++GST_RTSP_SERVER_API
++GstRTSPMediaExt * gst_rtsp_media_ext_new (GstElement *element);
++
++GST_RTSP_SERVER_API
++guint gst_rtsp_media_ext_get_resent_packets (GstRTSPMediaExt *media);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_ext_set_extended_mode (GstRTSPMediaExt *media, GstRTSPMediaExtMode mode);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_ext_set_retrans_port (GstRTSPMediaExt *media, guint port);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_ext_set_fec_value (GstRTSPMediaExt *media, guint max_k, guint max_p);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_ext_set_latency_mode (GstRTSPMediaExt *media, GstRTSPMediaExtLatency latency);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_ext_set_next_param (GstRTSPMediaExt *media, gint32 next_k, gint32 next_p);
++
++G_END_DECLS
++
++#endif /* __GST_RTSP_MEDIA_EXT_H__ */
--- /dev/null
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ *
+ * 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-uri
+ * @short_description: A factory for URI sources
+ * @see_also: #GstRTSPMediaFactory, #GstRTSPMedia
+ *
+ * This specialized #GstRTSPMediaFactory constructs media pipelines from a URI,
+ * given with gst_rtsp_media_factory_uri_set_uri().
+ *
+ * It will automatically demux and payload the different streams found in the
+ * media at URL.
+ *
+ * Last reviewed on 2013-07-11 (1.0.0)
+ */
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+
+ #include <string.h>
+
+ #include "rtsp-media-factory-uri.h"
+
+ struct _GstRTSPMediaFactoryURIPrivate
+ {
+ GMutex lock;
+ gchar *uri; /* protected by lock */
+ gboolean use_gstpay;
+
+ GstCaps *raw_vcaps;
+ GstCaps *raw_acaps;
+ GList *demuxers;
+ GList *payloaders;
+ GList *decoders;
+ };
+
+ #define DEFAULT_URI NULL
+ #define DEFAULT_USE_GSTPAY FALSE
+
+ enum
+ {
+ PROP_0,
+ PROP_URI,
+ PROP_USE_GSTPAY,
+ PROP_LAST
+ };
+
+
+ #define RAW_VIDEO_CAPS \
+ "video/x-raw"
+
+ #define RAW_AUDIO_CAPS \
+ "audio/x-raw"
+
+ static GstStaticCaps raw_video_caps = GST_STATIC_CAPS (RAW_VIDEO_CAPS);
+ static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS (RAW_AUDIO_CAPS);
+
+ typedef struct
+ {
+ GstRTSPMediaFactoryURI *factory;
+ guint pt;
+ } FactoryData;
+
+ static void
+ free_data (FactoryData * data)
+ {
+ g_object_unref (data->factory);
+ g_free (data);
+ }
+
+ static const gchar *factory_key = "GstRTSPMediaFactoryURI";
+
+ GST_DEBUG_CATEGORY_STATIC (rtsp_media_factory_uri_debug);
+ #define GST_CAT_DEFAULT rtsp_media_factory_uri_debug
+
+ static void gst_rtsp_media_factory_uri_get_property (GObject * object,
+ guint propid, GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_media_factory_uri_set_property (GObject * object,
+ guint propid, const GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_media_factory_uri_finalize (GObject * obj);
+
+ static GstElement *rtsp_media_factory_uri_create_element (GstRTSPMediaFactory *
+ factory, const GstRTSPUrl * url);
+
+ G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMediaFactoryURI, gst_rtsp_media_factory_uri,
+ GST_TYPE_RTSP_MEDIA_FACTORY);
+
+ static void
+ gst_rtsp_media_factory_uri_class_init (GstRTSPMediaFactoryURIClass * klass)
+ {
+ GObjectClass *gobject_class;
+ GstRTSPMediaFactoryClass *mediafactory_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ mediafactory_class = GST_RTSP_MEDIA_FACTORY_CLASS (klass);
+
+ gobject_class->get_property = gst_rtsp_media_factory_uri_get_property;
+ gobject_class->set_property = gst_rtsp_media_factory_uri_set_property;
+ gobject_class->finalize = gst_rtsp_media_factory_uri_finalize;
+
+ /**
+ * GstRTSPMediaFactoryURI::uri:
+ *
+ * The uri of the resource that will be served by this factory.
+ */
+ g_object_class_install_property (gobject_class, PROP_URI,
+ g_param_spec_string ("uri", "URI",
+ "The URI of the resource to stream", DEFAULT_URI,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPMediaFactoryURI::use-gstpay:
+ *
+ * Allow the usage of gstpay in order to avoid decoding of compressed formats
+ * without a payloader.
+ */
+ g_object_class_install_property (gobject_class, PROP_USE_GSTPAY,
+ g_param_spec_boolean ("use-gstpay", "Use gstpay",
+ "Use the gstpay payloader to avoid decoding", DEFAULT_USE_GSTPAY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ mediafactory_class->create_element = rtsp_media_factory_uri_create_element;
+
+ GST_DEBUG_CATEGORY_INIT (rtsp_media_factory_uri_debug, "rtspmediafactoryuri",
+ 0, "GstRTSPMediaFactoryUri");
+ }
+
+ typedef struct
+ {
+ GList *demux;
+ GList *payload;
+ GList *decode;
+ } FilterData;
+
+ static gboolean
+ payloader_filter (GstPluginFeature * feature, FilterData * data)
+ {
+ const gchar *klass;
+ GstElementFactory *fact;
+ GList **list = NULL;
+
+ /* we only care about element factories */
+ if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature)))
+ return FALSE;
+
+ if (gst_plugin_feature_get_rank (feature) < GST_RANK_MARGINAL)
+ return FALSE;
+
+ fact = GST_ELEMENT_FACTORY_CAST (feature);
+
+ klass = gst_element_factory_get_metadata (fact, GST_ELEMENT_METADATA_KLASS);
+
+ if (strstr (klass, "Decoder"))
+ list = &data->decode;
+ else if (strstr (klass, "Demux"))
+ list = &data->demux;
+ else if (strstr (klass, "Parser") && strstr (klass, "Codec"))
+ list = &data->demux;
+ else if (strstr (klass, "Payloader") && strstr (klass, "RTP"))
+ list = &data->payload;
+
+ if (list) {
+ GST_DEBUG ("adding %s", GST_OBJECT_NAME (fact));
+ *list = g_list_prepend (*list, gst_object_ref (fact));
+ }
+
+ return FALSE;
+ }
+
+ static void
+ gst_rtsp_media_factory_uri_init (GstRTSPMediaFactoryURI * factory)
+ {
+ GstRTSPMediaFactoryURIPrivate *priv =
+ gst_rtsp_media_factory_uri_get_instance_private (factory);
+ FilterData data = { NULL, NULL, NULL };
+
+ GST_DEBUG_OBJECT (factory, "new");
+
+ factory->priv = priv;
+
+ priv->uri = g_strdup (DEFAULT_URI);
+ priv->use_gstpay = DEFAULT_USE_GSTPAY;
+ g_mutex_init (&priv->lock);
+
+ /* get the feature list using the filter */
+ gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter)
+ payloader_filter, FALSE, &data);
+ /* sort */
+ priv->demuxers =
+ g_list_sort (data.demux, gst_plugin_feature_rank_compare_func);
+ priv->payloaders =
+ g_list_sort (data.payload, gst_plugin_feature_rank_compare_func);
+ priv->decoders =
+ g_list_sort (data.decode, gst_plugin_feature_rank_compare_func);
+
+ priv->raw_vcaps = gst_static_caps_get (&raw_video_caps);
+ priv->raw_acaps = gst_static_caps_get (&raw_audio_caps);
+ }
+
+ static void
+ gst_rtsp_media_factory_uri_finalize (GObject * obj)
+ {
+ GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (obj);
+ GstRTSPMediaFactoryURIPrivate *priv = factory->priv;
+
+ GST_DEBUG_OBJECT (factory, "finalize");
+
+ g_free (priv->uri);
+ gst_plugin_feature_list_free (priv->demuxers);
+ gst_plugin_feature_list_free (priv->payloaders);
+ gst_plugin_feature_list_free (priv->decoders);
+ gst_caps_unref (priv->raw_vcaps);
+ gst_caps_unref (priv->raw_acaps);
+ g_mutex_clear (&priv->lock);
+
+ G_OBJECT_CLASS (gst_rtsp_media_factory_uri_parent_class)->finalize (obj);
+ }
+
+ static void
+ gst_rtsp_media_factory_uri_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (object);
+ GstRTSPMediaFactoryURIPrivate *priv = factory->priv;
+
+ switch (propid) {
+ case PROP_URI:
+ g_value_take_string (value, gst_rtsp_media_factory_uri_get_uri (factory));
+ break;
+ case PROP_USE_GSTPAY:
+ g_value_set_boolean (value, priv->use_gstpay);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ static void
+ gst_rtsp_media_factory_uri_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPMediaFactoryURI *factory = GST_RTSP_MEDIA_FACTORY_URI (object);
+ GstRTSPMediaFactoryURIPrivate *priv = factory->priv;
+
+ switch (propid) {
+ case PROP_URI:
+ gst_rtsp_media_factory_uri_set_uri (factory, g_value_get_string (value));
+ break;
+ case PROP_USE_GSTPAY:
+ priv->use_gstpay = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ /**
+ * gst_rtsp_media_factory_uri_new:
+ *
+ * Create a new #GstRTSPMediaFactoryURI instance.
+ *
+ * Returns: (transfer full): a new #GstRTSPMediaFactoryURI object.
+ */
+ GstRTSPMediaFactoryURI *
+ gst_rtsp_media_factory_uri_new (void)
+ {
+ GstRTSPMediaFactoryURI *result;
+
+ result = g_object_new (GST_TYPE_RTSP_MEDIA_FACTORY_URI, NULL);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_media_factory_uri_set_uri:
+ * @factory: a #GstRTSPMediaFactory
+ * @uri: the uri the stream
+ *
+ * Set the URI of the resource that will be streamed by this factory.
+ */
+ void
+ gst_rtsp_media_factory_uri_set_uri (GstRTSPMediaFactoryURI * factory,
+ const gchar * uri)
+ {
+ GstRTSPMediaFactoryURIPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA_FACTORY_URI (factory));
+ g_return_if_fail (uri != NULL);
+
+ priv = factory->priv;
+
+ g_mutex_lock (&priv->lock);
+ g_free (priv->uri);
+ priv->uri = g_strdup (uri);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_factory_uri_get_uri:
+ * @factory: a #GstRTSPMediaFactory
+ *
+ * Get the URI that will provide media for this factory.
+ *
+ * Returns: (transfer full): the configured URI. g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_media_factory_uri_get_uri (GstRTSPMediaFactoryURI * factory)
+ {
+ GstRTSPMediaFactoryURIPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA_FACTORY_URI (factory), NULL);
+
+ priv = factory->priv;
+
+ g_mutex_lock (&priv->lock);
+ result = g_strdup (priv->uri);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ static GstElementFactory *
+ find_payloader (GstRTSPMediaFactoryURI * urifact, GstCaps * caps)
+ {
+ GstRTSPMediaFactoryURIPrivate *priv = urifact->priv;
+ GList *list;
+ GstElementFactory *factory = NULL;
+ gboolean autoplug_more = FALSE;
+
+ /* first find a demuxer that can link */
+ list = gst_element_factory_list_filter (priv->demuxers, caps,
+ GST_PAD_SINK, FALSE);
+
+ if (list) {
+ GstStructure *structure = gst_caps_get_structure (caps, 0);
+ gboolean parsed = FALSE;
+ gint mpegversion = 0;
+
+ if (!gst_structure_get_boolean (structure, "parsed", &parsed) &&
+ gst_structure_has_name (structure, "audio/mpeg") &&
+ gst_structure_get_int (structure, "mpegversion", &mpegversion) &&
+ (mpegversion == 2 || mpegversion == 4)) {
+ /* for AAC it's framed=true instead of parsed=true */
+ gst_structure_get_boolean (structure, "framed", &parsed);
+ }
+
+ /* Avoid plugging parsers in a loop. This is not 100% correct, as some
+ * parsers don't set parsed=true in caps. We should do something like
+ * decodebin does and track decode chains and elements plugged in those
+ * chains...
+ */
+ if (parsed) {
+ GList *walk;
+ const gchar *klass;
+
+ for (walk = list; walk; walk = walk->next) {
+ factory = GST_ELEMENT_FACTORY (walk->data);
+ klass = gst_element_factory_get_metadata (factory,
+ GST_ELEMENT_METADATA_KLASS);
+ if (strstr (klass, "Parser"))
+ /* caps have parsed=true, so skip this parser to avoid loops */
+ continue;
+
+ autoplug_more = TRUE;
+ break;
+ }
+ } else {
+ /* caps don't have parsed=true set and we have a demuxer/parser */
+ autoplug_more = TRUE;
+ }
+
+ gst_plugin_feature_list_free (list);
+ }
+
+ if (autoplug_more)
+ /* we have a demuxer, try that one first */
+ return NULL;
+
+ /* no demuxer try a depayloader */
+ list = gst_element_factory_list_filter (priv->payloaders, caps,
+ GST_PAD_SINK, FALSE);
+
+ if (list == NULL) {
+ if (priv->use_gstpay) {
+ /* no depayloader or parser/demuxer, use gstpay when allowed */
+ factory = gst_element_factory_find ("rtpgstpay");
+ } else {
+ /* no depayloader, try a decoder, we'll get to a payloader for a decoded
+ * video or audio format, worst case. */
+ list = gst_element_factory_list_filter (priv->decoders, caps,
+ GST_PAD_SINK, FALSE);
+
+ if (list != NULL) {
+ /* we have a decoder, try that one first */
+ gst_plugin_feature_list_free (list);
+ return NULL;
+ }
+ }
+ }
+
+ if (list != NULL) {
+ factory = GST_ELEMENT_FACTORY_CAST (list->data);
+ g_object_ref (factory);
+ gst_plugin_feature_list_free (list);
+ }
+ return factory;
+ }
+
+ static gboolean
+ autoplug_continue_cb (GstElement * uribin, GstPad * pad, GstCaps * caps,
+ GstElement * element)
+ {
+ FactoryData *data;
+ GstElementFactory *factory;
+
+ GST_DEBUG ("found pad %s:%s of caps %" GST_PTR_FORMAT,
+ GST_DEBUG_PAD_NAME (pad), caps);
+
+ data = g_object_get_data (G_OBJECT (element), factory_key);
+
+ if (!(factory = find_payloader (data->factory, caps)))
+ goto no_factory;
+
+ /* we found a payloader, stop autoplugging so we can plug the
+ * payloader. */
+ GST_DEBUG ("found factory %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+ gst_object_unref (factory);
+
+ return FALSE;
+
+ /* ERRORS */
+ no_factory:
+ {
+ /* no payloader, continue autoplugging */
+ GST_DEBUG ("no payloader found");
+ return TRUE;
+ }
+ }
+
+ static void
+ pad_added_cb (GstElement * uribin, GstPad * pad, GstElement * element)
+ {
+ GstRTSPMediaFactoryURI *urifact;
+ GstRTSPMediaFactoryURIPrivate *priv;
+ FactoryData *data;
+ GstElementFactory *factory;
+ GstElement *payloader;
+ GstCaps *caps;
+ GstPad *sinkpad, *srcpad, *ghostpad;
+ GstElement *convert;
+ gchar *padname, *payloader_name;
+
+ GST_DEBUG ("added pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ /* link the element now and expose the pad */
+ data = g_object_get_data (G_OBJECT (element), factory_key);
+ urifact = data->factory;
+ priv = urifact->priv;
+
+ /* ref to make refcounting easier later */
+ gst_object_ref (pad);
+ padname = gst_pad_get_name (pad);
+
+ /* get pad caps first, then call get_caps, then fail */
+ if ((caps = gst_pad_get_current_caps (pad)) == NULL)
+ if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
+ goto no_caps;
+
+ /* check for raw caps */
+ if (gst_caps_can_intersect (caps, priv->raw_vcaps)) {
+ /* we have raw video caps, insert converter */
+ convert = gst_element_factory_make ("videoconvert", NULL);
+ } else if (gst_caps_can_intersect (caps, priv->raw_acaps)) {
+ /* we have raw audio caps, insert converter */
+ convert = gst_element_factory_make ("audioconvert", NULL);
+ } else {
+ convert = NULL;
+ }
+
+ if (convert) {
+ gst_bin_add (GST_BIN_CAST (element), convert);
+ gst_element_set_state (convert, GST_STATE_PLAYING);
+
+ sinkpad = gst_element_get_static_pad (convert, "sink");
+ gst_pad_link (pad, sinkpad);
+ gst_object_unref (sinkpad);
+
+ /* unref old pad, we reffed before */
+ gst_object_unref (pad);
+ gst_caps_unref (caps);
+
+ /* continue with new pad and caps */
+ pad = gst_element_get_static_pad (convert, "src");
+ if ((caps = gst_pad_get_current_caps (pad)) == NULL)
+ if ((caps = gst_pad_query_caps (pad, NULL)) == NULL)
+ goto no_caps;
+ }
+
+ if (!(factory = find_payloader (urifact, caps)))
+ goto no_factory;
+
+ gst_caps_unref (caps);
+
+ /* we have a payloader now */
+ GST_DEBUG ("found payloader factory %s",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+
+ payloader_name = g_strdup_printf ("pay_%s", padname);
+ payloader = gst_element_factory_create (factory, payloader_name);
+ g_free (payloader_name);
+ if (payloader == NULL)
+ goto no_payloader;
+
+ g_object_set (payloader, "pt", data->pt, NULL);
+ data->pt++;
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (payloader),
+ "buffer-list"))
+ g_object_set (payloader, "buffer-list", TRUE, NULL);
+
+ /* add the payloader to the pipeline */
+ gst_bin_add (GST_BIN_CAST (element), payloader);
+ gst_element_set_state (payloader, GST_STATE_PLAYING);
+
+ /* link the pad to the sinkpad of the payloader */
+ sinkpad = gst_element_get_static_pad (payloader, "sink");
+ gst_pad_link (pad, sinkpad);
+ gst_object_unref (sinkpad);
+ gst_object_unref (pad);
+
+ /* now expose the srcpad of the payloader as a ghostpad with the same name
+ * as the uridecodebin pad name. */
+ srcpad = gst_element_get_static_pad (payloader, "src");
+ ghostpad = gst_ghost_pad_new (padname, srcpad);
+ gst_object_unref (srcpad);
+ g_free (padname);
+
+ gst_pad_set_active (ghostpad, TRUE);
+ gst_element_add_pad (element, ghostpad);
+
+ return;
+
+ /* ERRORS */
+ no_caps:
+ {
+ GST_WARNING ("could not get caps from pad");
+ g_free (padname);
+ gst_object_unref (pad);
+ return;
+ }
+ no_factory:
+ {
+ GST_DEBUG ("no payloader found");
+ g_free (padname);
+ gst_caps_unref (caps);
+ gst_object_unref (pad);
+ return;
+ }
+ no_payloader:
+ {
+ GST_ERROR ("could not create payloader from factory");
+ g_free (padname);
+ gst_caps_unref (caps);
+ gst_object_unref (pad);
+ return;
+ }
+ }
+
+ static void
+ no_more_pads_cb (GstElement * uribin, GstElement * element)
+ {
+ GST_DEBUG ("no-more-pads");
+ gst_element_no_more_pads (element);
+ }
+
+ static GstElement *
+ rtsp_media_factory_uri_create_element (GstRTSPMediaFactory * factory,
+ const GstRTSPUrl * url)
+ {
+ GstRTSPMediaFactoryURIPrivate *priv;
+ GstElement *topbin, *element, *uribin;
+ GstRTSPMediaFactoryURI *urifact;
+ FactoryData *data;
+
+ urifact = GST_RTSP_MEDIA_FACTORY_URI_CAST (factory);
+ priv = urifact->priv;
+
+ GST_LOG ("creating element");
+
+ topbin = gst_bin_new ("GstRTSPMediaFactoryURI");
+ g_assert (topbin != NULL);
+
+ /* our bin will dynamically expose payloaded pads */
+ element = gst_bin_new ("dynpay0");
+ g_assert (element != NULL);
+
+ uribin = gst_element_factory_make ("uridecodebin", "uribin");
+ if (uribin == NULL)
+ goto no_uridecodebin;
+
+ g_object_set (uribin, "uri", priv->uri, NULL);
+
+ /* keep factory data around */
+ data = g_new0 (FactoryData, 1);
+ data->factory = g_object_ref (urifact);
+ data->pt = 96;
+
+ g_object_set_data_full (G_OBJECT (element), factory_key,
+ data, (GDestroyNotify) free_data);
+
+ /* connect to the signals */
+ g_signal_connect (uribin, "autoplug-continue",
+ (GCallback) autoplug_continue_cb, element);
+ g_signal_connect (uribin, "pad-added", (GCallback) pad_added_cb, element);
+ g_signal_connect (uribin, "no-more-pads", (GCallback) no_more_pads_cb,
+ element);
+
+ gst_bin_add (GST_BIN_CAST (element), uribin);
+ gst_bin_add (GST_BIN_CAST (topbin), element);
+
+ return topbin;
+
+ no_uridecodebin:
+ {
+ g_critical ("can't create uridecodebin element");
+ gst_object_unref (element);
++ gst_object_unref (topbin);
+ return NULL;
+ }
+ }
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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)
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <stdio.h>
++#include <string.h>
++
++#include "rtsp-media-factory-wfd.h"
++#include "gstwfdmessage.h"
++#include "rtsp-media-ext.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)))
++
++typedef struct _GstRTPSMediaWFDTypeFindResult GstRTPSMediaWFDTypeFindResult;
++
++struct _GstRTPSMediaWFDTypeFindResult{
++ gint h264_found;
++ gint aac_found;
++ gint ac3_found;
++ GstElementFactory *demux_fact;
++ GstElementFactory *src_fact;
++};
++
++typedef struct _GstRTSPMediaWFDDirectPipelineData GstRTSPMediaWFDDirectPipelineData;
++
++struct _GstRTSPMediaWFDDirectPipelineData {
++ GstBin *pipeline;
++ GstElement *ap;
++ GstElement *vp;
++ GstElement *aq;
++ GstElement *vq;
++ GstElement *tsmux;
++ GstElement *mux_fs;
++ gchar *uri;
++};
++
++
++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;
++ GstBin *video_srcbin;
++
++ GstElement *venc;
++ guint decide_udp_bitrate[21];
++ guint min_udp_bitrate;
++ guint max_udp_bitrate;
++ gboolean decided_udp_bitrate;
++
++ 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;
++ GstBin *audio_srcbin;
++
++ GMutex direct_lock;
++ GCond direct_cond;
++ GType decodebin_type;
++ GstBin *discover_pipeline;
++ GstRTPSMediaWFDTypeFindResult res;
++ GstRTSPMediaWFDDirectPipelineData *direct_pipe;
++ GstBin *stream_bin;
++ GstElement *mux;
++ GstElement *mux_queue;
++ GstElement *pay;
++ GstElement *stub_fs;
++ GMainLoop *discover_loop;
++
++ 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_DIRECT_STREAMING_END,
++ SIGNAL_LAST
++};
++
++GST_DEBUG_CATEGORY_STATIC (rtsp_media_wfd_debug);
++#define GST_CAT_DEFAULT rtsp_media_wfd_debug
++
++static guint gst_rtsp_media_factory_wfd_signals[SIGNAL_LAST] = { 0 };
++
++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);
++
++static void _config_bitrate (GstRTSPMediaFactoryWFD * factory);
++
++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;
++
++ gst_rtsp_media_factory_wfd_signals[SIGNAL_DIRECT_STREAMING_END] =
++ g_signal_new ("direct-stream-end", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaFactoryWFDClass,
++ direct_stream_end), NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_NONE, 0, G_TYPE_NONE);
++
++ 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;
++ _config_bitrate (factory_wfd);
++}
++
++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;
++}
++
++void
++gst_rtsp_media_factory_wfd_set_video_codec (GstRTSPMediaFactory * factory,
++ guint video_codec)
++{
++ GstRTSPMediaFactoryWFD *factory_wfd = GST_RTSP_MEDIA_FACTORY_WFD (factory);
++ GstRTSPMediaFactoryWFDPrivate *priv = factory_wfd->priv;
++
++ priv->video_codec = video_codec;
++}
++
++static void
++_config_bitrate (GstRTSPMediaFactoryWFD * factory)
++{
++ GstRTSPMediaFactoryWFDPrivate *priv = factory->priv;
++
++ if (priv->decided_udp_bitrate) {
++ priv->video_bitrate = priv->decide_udp_bitrate[0];
++ priv->min_udp_bitrate = priv->decide_udp_bitrate[1];
++ priv->max_udp_bitrate = priv->decide_udp_bitrate[2];
++
++ if ((priv->video_width * priv->video_height) >= (1920 * 1080)) {
++ priv->video_bitrate = priv->decide_udp_bitrate[3];
++ priv->min_udp_bitrate = priv->decide_udp_bitrate[4];
++ priv->max_udp_bitrate = priv->decide_udp_bitrate[5];
++ } else if ((priv->video_width * priv->video_height) >= (1280 * 720)) {
++ priv->video_bitrate = priv->decide_udp_bitrate[6];
++ priv->min_udp_bitrate = priv->decide_udp_bitrate[7];
++ priv->max_udp_bitrate = priv->decide_udp_bitrate[8];
++ } else if ((priv->video_width * priv->video_height) >= (960 * 540)) {
++ priv->video_bitrate = priv->decide_udp_bitrate[9];
++ priv->min_udp_bitrate = priv->decide_udp_bitrate[10];
++ priv->max_udp_bitrate = priv->decide_udp_bitrate[11];
++ } else if ((priv->video_width * priv->video_height) >= (854 * 480)) {
++ priv->video_bitrate = priv->decide_udp_bitrate[12];
++ priv->min_udp_bitrate = priv->decide_udp_bitrate[13];
++ priv->max_udp_bitrate = priv->decide_udp_bitrate[14];
++ } else if ((priv->video_width * priv->video_height) >= (640 * 480)) {
++ priv->video_bitrate = priv->decide_udp_bitrate[15];
++ priv->min_udp_bitrate = priv->decide_udp_bitrate[16];
++ priv->max_udp_bitrate = priv->decide_udp_bitrate[17];
++ }
++ }
++}
++
++void
++gst_rtsp_media_factory_wfd_set_venc_bitrate (GstRTSPMediaFactory * factory,
++ gint bitrate)
++{
++ GstRTSPMediaFactoryWFD *factory_wfd = GST_RTSP_MEDIA_FACTORY_WFD (factory);
++ GstRTSPMediaFactoryWFDPrivate *priv = factory_wfd->priv;
++
++ g_object_set (priv->venc, "target-bitrate", bitrate, NULL);
++ priv->video_bitrate = (guint) bitrate;
++}
++
++void
++gst_rtsp_media_factory_wfd_get_venc_bitrate (GstRTSPMediaFactory * factory,
++ gint * bitrate)
++{
++ int cur_bitrate = 0;
++
++ GstRTSPMediaFactoryWFD *factory_wfd = GST_RTSP_MEDIA_FACTORY_WFD (factory);
++ GstRTSPMediaFactoryWFDPrivate *priv = factory_wfd->priv;
++
++ g_object_get (priv->venc, "target-bitrate", &cur_bitrate, NULL);
++
++ if (cur_bitrate == 0) {
++ *bitrate = priv->video_bitrate;
++ } else {
++ *bitrate = (gint) cur_bitrate;
++ }
++}
++
++void
++gst_rtsp_media_factory_wfd_get_config_bitrate (GstRTSPMediaFactory * factory,
++ guint32 * min, guint32 * max)
++{
++ GstRTSPMediaFactoryWFD *factory_wfd = GST_RTSP_MEDIA_FACTORY_WFD (factory);
++ GstRTSPMediaFactoryWFDPrivate *priv = factory_wfd->priv;
++
++ *min = priv->min_udp_bitrate;
++ *max = priv->max_udp_bitrate;
++}
++
++void
++gst_rtsp_media_factory_wfd_set_config_bitrate (GstRTSPMediaFactoryWFD * factory,
++ guint * config_bitrate)
++{
++ GstRTSPMediaFactoryWFDPrivate *priv = factory->priv;
++
++ gint idx = 0;
++ for (idx = 0; idx < 21; idx++) {
++ priv->decide_udp_bitrate[idx] = config_bitrate[idx];
++ }
++ priv->decided_udp_bitrate = TRUE;
++
++ _config_bitrate (factory);
++}
++
++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->video_srcbin = NULL;
++ priv->min_udp_bitrate = 938861;
++ priv->max_udp_bitrate = 1572864;
++ priv->decided_udp_bitrate = FALSE;
++
++ 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;
++ priv->audio_srcbin = NULL;
++
++ g_mutex_init (&priv->direct_lock);
++ g_cond_init (&priv->direct_cond);
++
++ priv->discover_pipeline = NULL;
++ priv->direct_pipe = NULL;
++ memset (&priv->res, 0x00, sizeof (GstRTPSMediaWFDTypeFindResult));
++ priv->stream_bin = NULL;
++ priv->mux = NULL;
++ priv->mux_queue = NULL;
++ priv->pay = NULL;
++
++ 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);
++
++ g_mutex_clear (&priv->direct_lock);
++ g_cond_clear (&priv->direct_cond);
++
++ 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;
++ GstStructure *audio_properties_name = NULL;
++
++ guint channels = 0;
++ gboolean is_enc_req = TRUE;
++ guint freq = 0;
++ g_autofree gchar *acodec = NULL;
++
++ priv = factory->priv;
++
++ if (priv->audio_codec == GST_WFD_AUDIO_UNKNOWN) {
++ GST_INFO_OBJECT (factory, "Skip create audio source");
++ return TRUE;
++ }
++
++ priv->audio_srcbin = (GstBin *)gst_bin_new ("audio");
++
++ /* 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);
++
++ 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,
++ 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 (priv->audio_srcbin, audiosrc, acaps, aenc, aqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->audio_srcbin));
++
++ 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 (priv->audio_srcbin, audiosrc, acaps2, audio_convert, acaps, aqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->audio_srcbin));
++
++ 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 (audio_properties_name)
++ gst_structure_free (audio_properties_name);
++ return TRUE;
++
++create_error:
++ gst_object_unref (acaps);
++ gst_object_unref (aqueue);
++ if (audio_properties_name)
++ gst_structure_free (audio_properties_name);
++ 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;
++ GstElement *venc = NULL;
++ GstElement *vparse = NULL;
++ GstElement *vqueue = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ priv = factory->priv;
++
++ GST_INFO_OBJECT (factory, "picked videotestsrc as video source");
++ priv->video_srcbin = (GstBin *)gst_bin_new ("video");
++
++ 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) {
++ GST_ERROR_OBJECT (factory, "Yet to support other than H264 format");
++ goto create_error;
++ }
++
++ venc = gst_element_factory_make (priv->video_encoder, "videoenc");
++ 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 (priv->video_srcbin, videosrc, vcaps, videoconvert, venc_caps, venc, vparse, vqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->video_srcbin));
++ 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;
++ priv->venc = venc;
++
++ return TRUE;
++
++create_error:
++ gst_object_unref(videosrc);
++ gst_object_unref(vcaps);
++ gst_object_unref(videoconvert);
++ gst_object_unref(venc_caps);
++ gst_object_unref(venc);
++ gst_object_unref(vparse);
++ gst_object_unref(vqueue);
++ return FALSE;
++}
++
++static gboolean
++_rtsp_media_factory_wfd_create_waylandsrc_bin (GstRTSPMediaFactoryWFD * factory,
++ GstBin * srcbin)
++{
++ GstElement *videosrc = NULL;
++ GstElement *vcaps = 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");
++
++ if (priv->video_codec == GST_WFD_VIDEO_UNKNOWN) {
++ GST_INFO_OBJECT (factory, "Skip create video source.");
++ return TRUE;
++ }
++
++ priv->video_srcbin = (GstBin *)gst_bin_new ("video");
++
++ 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) {
++ GST_ERROR_OBJECT (factory, "Yet to support other than H264 format");
++ goto create_error;
++ }
++
++ venc = gst_element_factory_make (priv->video_encoder, "videoenc");
++ 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);
++ g_object_set (venc, "target-bitrate", priv->video_bitrate, 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 (priv->video_srcbin, videosrc, vcaps, venc, vparse, vqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->video_srcbin));
++ 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;
++ priv->venc = venc;
++
++ return TRUE;
++
++create_error:
++ gst_object_unref (videosrc);
++ gst_object_unref (vqueue);
++ 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;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ priv = factory->priv;
++ priv->video_srcbin = (GstBin *)gst_bin_new ("video");
++
++ 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) {
++ GST_ERROR_OBJECT (factory, "Yet to support other than H264 format");
++ goto create_error;
++ }
++
++ venc = gst_element_factory_make (priv->video_encoder, "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);
++
++ 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 (priv->video_srcbin, videosrc, vcaps, venc, vparse, vqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->video_srcbin));
++
++ 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;
++ priv->venc = venc;
++
++ return TRUE;
++
++create_error:
++ gst_object_unref (videosrc);
++ gst_object_unref (vqueue);
++ 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;
++ GstElement *venc = NULL;
++ GstElement *vparse = NULL;
++ GstElement *vqueue = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ priv = factory->priv;
++
++ GST_INFO_OBJECT (factory, "picked ximagesrc as video source");
++ priv->video_srcbin = (GstBin *)gst_bin_new ("video");
++
++ 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) {
++ GST_ERROR_OBJECT (factory, "Yet to support other than H264 format");
++ goto create_error;
++ }
++
++ venc = gst_element_factory_make (priv->video_encoder, "videoenc");
++ 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 (priv->video_srcbin, videosrc, videoscale, videoconvert, vcaps, venc,
++ venc_caps, vparse, vqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->video_srcbin));
++ 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;
++ priv->venc = venc;
++
++ return TRUE;
++
++create_error:
++ gst_object_unref(videosrc);
++ gst_object_unref(vcaps);
++ gst_object_unref(venc_caps);
++ gst_object_unref(videoconvert);
++ gst_object_unref(videoscale);
++ gst_object_unref(venc);
++ gst_object_unref(vparse);
++ gst_object_unref(vqueue);
++ return FALSE;
++}
++
++static gboolean
++_rtsp_media_factory_wfd_create_xvcapture_bin (GstRTSPMediaFactoryWFD * factory,
++ GstBin * srcbin)
++{
++ GstElement *videosrc = NULL;
++ GstElement *vcaps = 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");
++ priv->video_srcbin = (GstBin *)gst_bin_new ("video");
++
++ 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) {
++ GST_ERROR_OBJECT (factory, "Yet to support other than H264 format");
++ goto create_error;
++ }
++
++ venc = gst_element_factory_make (priv->video_encoder, "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 (priv->video_srcbin, videosrc, vcaps, venc, vparse, vqueue, NULL);
++ gst_bin_add (srcbin, GST_ELEMENT (priv->video_srcbin));
++ 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;
++ priv->venc = venc;
++
++ return TRUE;
++
++create_error:
++ gst_object_unref (videosrc);
++ gst_object_unref (vqueue);
++
++ 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;
++ GstPad *ghost_pad = 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;
++ }
++
++ GST_INFO_OBJECT (factory, "Check video codec... %d", priv->video_codec);
++ /* 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;
++ }
++
++ if (priv->video_codec > GST_WFD_VIDEO_UNKNOWN) {
++ /* 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;
++ }
++ ghost_pad = gst_ghost_pad_new ("video_src", srcpad);
++ gst_element_add_pad (GST_ELEMENT (priv->video_srcbin), ghost_pad);
++
++ if (gst_pad_link (ghost_pad, 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;
++ ghost_pad = NULL;
++ }
++
++ GST_INFO_OBJECT (factory, "Check audio codec... %d", priv->audio_codec);
++
++ /* create audio source elements & add to pipeline */
++ if (!_rtsp_media_factory_wfd_create_audio_capture_bin (factory, srcbin))
++ goto create_error;
++
++ if (priv->audio_codec > GST_WFD_AUDIO_UNKNOWN) {
++ /* 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;
++ }
++ ghost_pad = gst_ghost_pad_new ("audio_src", srcpad);
++ gst_element_add_pad (GST_ELEMENT (priv->audio_srcbin), ghost_pad);
++
++ /* link audio queue's srcpad & muxer sink pad */
++ if (gst_pad_link (ghost_pad, 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);
++ if (pad_probe)
++ gst_object_unref (pad_probe);
++ }
++
++ GST_DEBUG_OBJECT (factory, "successfully created source bin...");
++
++ priv->stream_bin = srcbin;
++ priv->mux = gst_object_ref (mux);
++ priv->mux_queue = gst_object_ref (mux_queue);
++ priv->pay = gst_object_ref (payload);
++
++ return GST_ELEMENT_CAST (srcbin);
++
++create_error:
++ GST_ERROR_OBJECT (factory, "Failed to create pipeline");
++ if (mux_vsinkpad)
++ gst_object_unref (mux_vsinkpad);
++ if (mux_asinkpad)
++ gst_object_unref (mux_asinkpad);
++ if (srcpad)
++ gst_object_unref (srcpad);
++ if (srcbin)
++ gst_object_unref (srcbin);
++ 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);
++ //media = g_object_new (GST_TYPE_RTSP_MEDIA_EXT, "element", element, NULL);
++
++ 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;
++ }
++}
++
++gint type_detected = FALSE;
++gint linked = FALSE;
++static gint in_pad_probe;
++
++static GstPadProbeReturn
++_rtsp_media_factory_wfd_restore_pipe_probe_cb (GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
++{
++ GstPad *old_src = NULL;
++ GstPad *sink = NULL;
++ GstPad *old_sink = NULL;
++ GstPad *new_src = NULL;
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstRTSPMediaWFDDirectPipelineData *pipe_data = NULL;
++
++ if (!g_atomic_int_compare_and_exchange (&in_pad_probe, FALSE, TRUE))
++ return GST_PAD_PROBE_OK;
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++ priv = factory->priv;
++ pipe_data = priv->direct_pipe;
++
++ gst_element_sync_state_with_parent (GST_ELEMENT(priv->audio_srcbin));
++ gst_element_sync_state_with_parent (GST_ELEMENT(priv->video_srcbin));
++ gst_element_sync_state_with_parent (GST_ELEMENT(priv->mux));
++ gst_element_sync_state_with_parent (GST_ELEMENT(priv->mux_queue));
++
++ sink = gst_element_get_static_pad (priv->pay, "sink");
++ old_src = gst_pad_get_peer (sink);
++ gst_pad_unlink (old_src, sink);
++
++ new_src = gst_element_get_static_pad (priv->mux_queue, "src");
++ old_sink = gst_pad_get_peer (new_src);
++ gst_pad_unlink (new_src, old_sink);
++ gst_element_set_state (priv->stub_fs, GST_STATE_NULL);
++ gst_bin_remove ((GstBin *)priv->stream_bin, priv->stub_fs);
++
++ gst_pad_link (new_src, sink);
++ gst_object_unref (new_src);
++ gst_object_unref (old_sink);
++
++ gst_element_set_state (GST_ELEMENT(pipe_data->pipeline), GST_STATE_PAUSED);
++
++ /* signal that new pipeline linked */
++ g_mutex_lock (&priv->direct_lock);
++ g_cond_signal (&priv->direct_cond);
++ linked = TRUE;
++ g_mutex_unlock (&priv->direct_lock);
++
++ return GST_PAD_PROBE_REMOVE;
++}
++
++static gboolean
++_rtsp_media_factory_wfd_destroy_direct_pipe(void *user_data)
++{
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstRTSPMediaWFDDirectPipelineData *pipe_data = NULL;
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++ priv = factory->priv;
++ pipe_data = priv->direct_pipe;
++
++ GST_DEBUG_OBJECT (factory, "Deleting pipeline");
++ gst_element_set_state (GST_ELEMENT(pipe_data->pipeline), GST_STATE_NULL);
++ gst_bin_remove ((GstBin *)priv->stream_bin, GST_ELEMENT(pipe_data->pipeline));
++ g_free (pipe_data);
++ g_signal_emit (factory,
++ gst_rtsp_media_factory_wfd_signals[SIGNAL_DIRECT_STREAMING_END], 0, NULL);
++ return FALSE;
++}
++
++static void
++_rtsp_media_factory_wfd_demux_pad_added_cb (GstElement *element,
++ GstPad *pad,
++ gpointer data)
++{
++ GstPad *sinkpad = NULL;
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstRTSPMediaWFDDirectPipelineData *pipe_data = NULL;
++
++ GstCaps *caps = gst_pad_get_current_caps (pad);
++ g_autofree gchar *pad_name = gst_pad_get_name (pad);
++ g_autofree gchar *caps_string = gst_caps_to_string (caps);
++ g_autofree gchar *temp_caps = NULL;
++
++ gst_caps_unref (caps);
++
++ factory = (GstRTSPMediaFactoryWFD *) data;
++ priv = factory->priv;
++ pipe_data = priv->direct_pipe;
++ temp_caps = g_ascii_strdown(caps_string, -1);
++
++ if (g_strrstr (temp_caps, "audio")) {
++ sinkpad = gst_element_get_static_pad (pipe_data->ap, "sink");
++ if (gst_pad_is_linked (sinkpad)) {
++ gst_object_unref (sinkpad);
++ GST_DEBUG_OBJECT (factory, "pad linked");
++ return;
++ }
++ if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)
++ GST_DEBUG_OBJECT (factory, "can't link demux %s pad", pad_name);
++
++ gst_object_unref (sinkpad);
++ sinkpad = NULL;
++ }
++
++ if (g_strrstr (temp_caps, "video")) {
++ if (g_strrstr (temp_caps, "h264")) {
++ sinkpad = gst_element_get_static_pad (pipe_data->vp, "sink");
++ if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)
++ GST_DEBUG_OBJECT (factory, "can't link demux %s pad", pad_name);
++
++ gst_object_unref (sinkpad);
++ sinkpad = NULL;
++ }
++ }
++}
++
++static GstPadProbeReturn
++_rtsp_media_factory_wfd_pay_pad_probe_cb (GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
++{
++ GstPad *old_src = NULL;
++ GstPad *sink = NULL;
++ GstPad *old_sink = NULL;
++ GstPad *new_src = NULL;
++ GstPad *fas_sink = NULL;
++ GstPad *gp = NULL;
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstRTSPMediaWFDDirectPipelineData *pipe_data = NULL;
++
++ if (!g_atomic_int_compare_and_exchange (&in_pad_probe, FALSE, TRUE))
++ return GST_PAD_PROBE_OK;
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++ priv = factory->priv;
++ pipe_data = priv->direct_pipe;
++
++ sink = gst_element_get_static_pad (priv->pay, "sink");
++ old_src = gst_pad_get_peer (sink);
++ gst_pad_unlink (old_src, sink);
++
++ new_src = gst_element_get_static_pad (pipe_data->tsmux, "src");
++ old_sink = gst_pad_get_peer (new_src);
++ gst_pad_unlink (new_src, old_sink);
++ gst_element_set_state (pipe_data->mux_fs, GST_STATE_NULL);
++ gst_bin_remove ((GstBin *)pipe_data->pipeline, pipe_data->mux_fs);
++
++ gp = gst_ghost_pad_new ("audio_file", new_src);
++ gst_pad_set_active(gp,TRUE);
++ gst_element_add_pad (GST_ELEMENT (pipe_data->pipeline), gp);
++ gst_pad_link (gp, sink);
++ gst_object_unref (new_src);
++ gst_object_unref (old_sink);
++
++ priv->stub_fs = gst_element_factory_make ("fakesink", NULL);
++ gst_bin_add (priv->stream_bin, priv->stub_fs);
++ gst_element_sync_state_with_parent (priv->stub_fs);
++ fas_sink = gst_element_get_static_pad (priv->stub_fs, "sink");
++ gst_pad_link (old_src, fas_sink);
++ gst_object_unref (old_src);
++ gst_object_unref (fas_sink);
++ gst_element_set_state (GST_ELEMENT(priv->audio_srcbin), GST_STATE_PAUSED);
++ gst_element_set_state (GST_ELEMENT(priv->video_srcbin), GST_STATE_PAUSED);
++ gst_element_set_state (GST_ELEMENT(priv->mux), GST_STATE_PAUSED);
++ gst_element_set_state (GST_ELEMENT(priv->mux_queue), GST_STATE_PAUSED);
++
++ /* signal that new pipeline linked */
++ g_mutex_lock (&priv->direct_lock);
++ linked = TRUE;
++ g_cond_signal (&priv->direct_cond);
++ g_mutex_unlock (&priv->direct_lock);
++
++ return GST_PAD_PROBE_REMOVE;
++}
++
++static gboolean
++_rtsp_media_factory_wfd_relink_pipeline(GstRTSPMediaFactoryWFD * factory)
++{
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstPad *probe_pad = NULL;
++ gint64 end_time = 0;
++
++ priv = factory->priv;
++
++ probe_pad = gst_element_get_static_pad (priv->pay, "sink");
++ if (probe_pad == NULL)
++ return FALSE;
++
++ in_pad_probe = FALSE;
++ linked = FALSE;
++ gst_pad_add_probe (probe_pad, GST_PAD_PROBE_TYPE_IDLE, _rtsp_media_factory_wfd_restore_pipe_probe_cb, factory, NULL);
++
++ g_mutex_lock (&factory->priv->direct_lock);
++ end_time = g_get_monotonic_time () + 5 * G_TIME_SPAN_SECOND;
++ if (!g_cond_wait_until (&factory->priv->direct_cond, &factory->priv->direct_lock, end_time)) {
++ g_mutex_unlock (&factory->priv->direct_lock);
++ GST_ERROR_OBJECT (factory, "Failed to relink pipeline");
++ return linked;
++ }
++ g_mutex_unlock (&factory->priv->direct_lock);
++ return linked;
++}
++
++
++static GstPadProbeReturn
++_rtsp_media_factory_wfd_src_pad_probe_cb(GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
++{
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++
++ if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
++ GST_INFO_OBJECT (factory, "Got event: %s in direct streaming", GST_EVENT_TYPE_NAME (event));
++ info->data = NULL;
++ info->data = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new_empty ("fillEOS"));
++
++ if (!_rtsp_media_factory_wfd_relink_pipeline(factory)) {
++ GST_ERROR_OBJECT (factory, "Failed to relink pipeline");
++ return GST_PAD_PROBE_REMOVE;
++ }
++
++ g_idle_add((GSourceFunc)_rtsp_media_factory_wfd_destroy_direct_pipe, factory);
++ return GST_PAD_PROBE_REMOVE;
++ }
++
++ return GST_PAD_PROBE_OK;
++}
++
++static gboolean
++_rtsp_media_factory_wfd_create_direct_pipeline(GstRTSPMediaFactoryWFD * factory)
++{
++ GstElement *src = NULL;
++ GstElement *demux = NULL;
++ g_autofree gchar *path = NULL;
++ g_autofree gchar *elem_name = NULL;
++ GstPad *srcpad = NULL;
++ GstPad *mux_vsinkpad = NULL;
++ GstPad *mux_asinkpad = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstRTSPMediaWFDDirectPipelineData *pipe_data = NULL;
++
++ priv = factory->priv;
++ pipe_data = priv->direct_pipe;
++
++ pipe_data->pipeline = (GstBin *) gst_bin_new ("direct");
++
++ src = gst_element_factory_create(priv->res.src_fact, NULL);
++ demux = gst_element_factory_create(priv->res.demux_fact, NULL);
++ pipe_data->ap = gst_element_factory_make ("aacparse", NULL);
++ pipe_data->vp = gst_element_factory_make ("h264parse", NULL);
++ pipe_data->aq = gst_element_factory_make ("queue", NULL);
++ pipe_data->vq = gst_element_factory_make ("queue", NULL);
++ pipe_data->tsmux = gst_element_factory_make ("mpegtsmux", NULL);
++ pipe_data->mux_fs = gst_element_factory_make ("fakesink", NULL);
++
++ if (src == NULL || demux == NULL || pipe_data->tsmux == NULL ||
++ pipe_data->ap == NULL || pipe_data->vp == NULL ||
++ pipe_data->aq == NULL || pipe_data->vq == NULL ||
++ pipe_data->mux_fs == NULL) {
++ GST_ERROR_OBJECT (factory, "Not all element created");
++ return FALSE;
++ }
++
++ elem_name = g_ascii_strdown(g_type_name(G_OBJECT_TYPE(src)), -1);
++
++ if (g_strrstr (elem_name, "file")) {
++ path = g_filename_from_uri (pipe_data->uri, NULL, NULL);
++
++ if (path == NULL) {
++ GST_ERROR_OBJECT(factory, "No file path");
++ return FALSE;
++ }
++ g_object_set (src, "location", path, NULL);
++ } else
++ g_object_set (src, "uri", pipe_data->uri, NULL);
++
++ gst_bin_add_many (pipe_data->pipeline, src, demux, pipe_data->ap,
++ pipe_data->vp, pipe_data->aq, pipe_data->vq,
++ pipe_data->tsmux, pipe_data->mux_fs, NULL);
++
++ if (!gst_element_link (src, demux)) {
++ GST_ERROR_OBJECT (factory, "Can't link src with demux");
++ return FALSE;
++ }
++
++ if (!gst_element_link (pipe_data->ap, pipe_data->aq)) {
++ GST_ERROR_OBJECT (factory, "Can't link audio parse and queue");
++ return FALSE;
++ }
++
++ if (!gst_element_link (pipe_data->vp, pipe_data->vq)) {
++ GST_ERROR_OBJECT (factory, "Can't link video parse and queue");
++ return FALSE;
++ }
++
++ if (!gst_element_link (pipe_data->tsmux, pipe_data->mux_fs)) {
++ GST_DEBUG_OBJECT (factory, "Can't link muxer and fakesink");
++ return FALSE;
++ }
++
++ g_signal_connect_object (demux, "pad-added", G_CALLBACK (_rtsp_media_factory_wfd_demux_pad_added_cb), factory, 0);
++
++ gst_bin_add (priv->stream_bin, GST_ELEMENT (pipe_data->pipeline));
++
++
++ /* request video sink pad from muxer, which has elementary pid 0x1011 */
++ mux_vsinkpad = gst_element_get_request_pad (pipe_data->tsmux, "sink_4113");
++ if (!mux_vsinkpad) {
++ GST_ERROR_OBJECT (factory, "Failed to get sink pad from muxer...");
++ return FALSE;
++ }
++
++ /* request srcpad from video queue */
++ srcpad = gst_element_get_static_pad (pipe_data->vq, "src");
++ if (!srcpad) {
++ GST_ERROR_OBJECT (factory, "Failed to get srcpad from video queue...");
++ }
++
++ 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...");
++ return FALSE;
++ }
++
++ gst_object_unref (mux_vsinkpad);
++ gst_object_unref (srcpad);
++ srcpad = NULL;
++
++ /* request audio sink pad from muxer, which has elementary pid 0x1100 */
++ mux_asinkpad = gst_element_get_request_pad (pipe_data->tsmux, "sink_4352");
++ if (!mux_asinkpad) {
++ GST_ERROR_OBJECT (factory, "Failed to get sinkpad from muxer...");
++ return FALSE;
++ }
++
++ /* request srcpad from audio queue */
++ srcpad = gst_element_get_static_pad (pipe_data->aq, "src");
++ if (!srcpad) {
++ GST_ERROR_OBJECT (factory, "Failed to get srcpad from audio queue...");
++ return FALSE;
++ }
++
++ /* 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...");
++ return FALSE;
++ }
++ gst_object_unref (mux_asinkpad);
++ gst_object_unref (srcpad);
++ srcpad = NULL;
++
++ gst_element_sync_state_with_parent (GST_ELEMENT (pipe_data->pipeline));
++
++ srcpad = gst_element_get_static_pad (priv->pay, "sink");
++
++ in_pad_probe = FALSE;
++ gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_IDLE, _rtsp_media_factory_wfd_pay_pad_probe_cb, factory, NULL);
++ gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, _rtsp_media_factory_wfd_src_pad_probe_cb, factory, NULL);
++
++ return TRUE;
++}
++
++static void
++_rtsp_media_factory_wfd_decodebin_element_added_cb (GstElement *decodebin,
++ GstElement *child, void *user_data)
++{
++ g_autofree gchar *elem_name = g_ascii_strdown(g_type_name(G_OBJECT_TYPE(child)), -1);
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++ priv = factory->priv;
++
++ if (g_strrstr (elem_name, "h264"))
++ priv->res.h264_found++;
++ if (g_strrstr (elem_name, "aac"))
++ priv->res.aac_found++;
++ if (g_strrstr (elem_name, "ac3"))
++ priv->res.ac3_found++;
++ if (g_strrstr (elem_name, "demux"))
++ priv->res.demux_fact = gst_element_get_factory(child);
++}
++
++static void
++_rtsp_media_factory_wfd_uridecodebin_element_added_cb (GstElement *uridecodebin,
++ GstElement *child, void *user_data)
++{
++ g_autofree gchar *elem_name = g_ascii_strdown(g_type_name(G_OBJECT_TYPE(child)), -1);
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++ priv = factory->priv;
++
++ if (g_strrstr (elem_name, "src"))
++ priv->res.src_fact = gst_element_get_factory(child);
++
++ if (G_OBJECT_TYPE(child) == priv->decodebin_type)
++ g_signal_connect_object (child, "element-added",
++ G_CALLBACK (_rtsp_media_factory_wfd_decodebin_element_added_cb), factory, 0);
++}
++
++static void
++_rtsp_media_factory_wfd_discover_pad_added_cb (GstElement *uridecodebin, GstPad *pad,
++ GstBin *pipeline)
++{
++ GstPad *sinkpad = NULL;
++ GstCaps *caps;
++
++ GstElement *queue = gst_element_factory_make ("queue", NULL);
++ GstElement *sink = gst_element_factory_make ("fakesink", NULL);
++
++ if (G_UNLIKELY (queue == NULL || sink == NULL))
++ goto error;
++
++ g_object_set (sink, "silent", TRUE, NULL);
++ g_object_set (queue, "max-size-buffers", 1, "silent", TRUE, NULL);
++
++ caps = gst_pad_query_caps (pad, NULL);
++
++ sinkpad = gst_element_get_static_pad (queue, "sink");
++ if (sinkpad == NULL)
++ goto error;
++
++ gst_caps_unref (caps);
++
++ gst_bin_add_many (pipeline, queue, sink, NULL);
++
++ if (!gst_element_link_pads_full (queue, "src", sink, "sink",
++ GST_PAD_LINK_CHECK_NOTHING))
++ goto error;
++ if (!gst_element_sync_state_with_parent (sink))
++ goto error;
++ if (!gst_element_sync_state_with_parent (queue))
++ goto error;
++
++ if (gst_pad_link_full (pad, sinkpad,
++ GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)
++ goto error;
++ gst_object_unref (sinkpad);
++
++ return;
++
++error:
++ if (sinkpad)
++ gst_object_unref (sinkpad);
++ if (queue)
++ gst_object_unref (queue);
++ if (sink)
++ gst_object_unref (sink);
++ return;
++}
++
++static void
++_rtsp_media_factory_wfd_uridecode_no_pad_cb (GstElement * uridecodebin, void * user_data)
++{
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ factory = (GstRTSPMediaFactoryWFD *) user_data;
++ priv = factory->priv;
++ type_detected = TRUE;
++ g_main_loop_quit (priv->discover_loop);
++}
++
++static void
++_rtsp_media_factory_wfd_discover_pipe_bus_call (GstBus *bus,
++ GstMessage *msg,
++ gpointer data)
++{
++ GstRTSPMediaFactoryWFD *factory = NULL;
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++
++ factory = (GstRTSPMediaFactoryWFD *) data;
++ priv = factory->priv;
++
++ switch (GST_MESSAGE_TYPE (msg)) {
++ case GST_MESSAGE_ERROR: {
++ GError *error = NULL;
++
++ gst_message_parse_error (msg, &error, NULL);
++
++ GST_ERROR_OBJECT (factory, "Error: %s", error->message);
++ g_error_free (error);
++
++ type_detected = FALSE;
++ g_main_loop_quit (priv->discover_loop);
++ break;
++ }
++ default:
++ break;
++ }
++}
++
++static gboolean
++_rtsp_media_factory_wfd_find_media_type (GstRTSPMediaFactoryWFD * factory, gchar *uri)
++{
++ GstRTSPMediaFactoryWFDPrivate *priv = NULL;
++ GstElement *uridecode = NULL;
++ GstElement *tmp = NULL;
++ GstBus *bus;
++ GMainContext *context;
++ GSource *source;
++
++ priv = factory->priv;
++
++ context = g_main_context_new();
++ priv->discover_loop = g_main_loop_new(context, FALSE);
++
++ tmp = gst_element_factory_make ("decodebin", NULL);
++ priv->decodebin_type = G_OBJECT_TYPE (tmp);
++ gst_object_unref (tmp);
++
++ /* if a URI was provided, use it instead of the default one */
++ priv->discover_pipeline = (GstBin *) gst_pipeline_new ("Discover");
++ uridecode = gst_element_factory_make("uridecodebin", "uri");
++ g_object_set (G_OBJECT (uridecode), "uri", uri, NULL);
++ gst_bin_add (priv->discover_pipeline, uridecode);
++ if (priv->decodebin_type == NULL || priv->discover_pipeline == NULL || uridecode == NULL) {
++ GST_INFO_OBJECT (factory, "Failed to create type find pipeline");
++ type_detected = FALSE;
++ return FALSE;
++ }
++
++ /* we add a message handler */
++ bus = gst_pipeline_get_bus (GST_PIPELINE (priv->discover_pipeline));
++ source = gst_bus_create_watch (bus);
++ gst_bus_add_signal_watch (bus);
++
++ g_source_set_callback (source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL);
++ g_source_attach (source, context);
++ g_signal_connect_object (bus, "message",
++ G_CALLBACK (_rtsp_media_factory_wfd_discover_pipe_bus_call), factory, 0);
++
++ g_signal_connect_object (uridecode, "pad-added",
++ G_CALLBACK (_rtsp_media_factory_wfd_discover_pad_added_cb), priv->discover_pipeline, 0);
++ g_signal_connect_object (uridecode, "element-added",
++ G_CALLBACK (_rtsp_media_factory_wfd_uridecodebin_element_added_cb),
++ factory, 0);
++ g_signal_connect_object (uridecode, "no-more-pads",
++ G_CALLBACK (_rtsp_media_factory_wfd_uridecode_no_pad_cb), factory, 0);
++ gst_element_set_state (GST_ELEMENT (priv->discover_pipeline), GST_STATE_PLAYING);
++
++ g_main_loop_run(priv->discover_loop);
++
++ gst_element_set_state (GST_ELEMENT (priv->discover_pipeline), GST_STATE_NULL);
++ g_source_destroy(source);
++ g_source_unref (source);
++ g_main_loop_unref(priv->discover_loop);
++ g_main_context_unref(context);
++ gst_object_unref(bus);
++ gst_object_unref (GST_OBJECT (priv->discover_pipeline));
++
++ return TRUE;
++}
++
++gint
++gst_rtsp_media_factory_wfd_uri_type_find(GstRTSPMediaFactory *factory,
++ gchar *filesrc, guint8 *detected_video_codec, guint8 *detected_audio_codec)
++{
++ GstRTSPMediaFactoryWFD *_factory = GST_RTSP_MEDIA_FACTORY_WFD_CAST (factory);
++ GstRTSPMediaFactoryWFDPrivate *priv = _factory->priv;
++
++ type_detected = FALSE;
++
++ _rtsp_media_factory_wfd_find_media_type (_factory, filesrc);
++
++ if (type_detected == FALSE) {
++ GST_ERROR_OBJECT (_factory, "Media type cannot be detected");
++ return GST_RTSP_ERROR;
++ }
++ GST_INFO_OBJECT (_factory, "Media type detected");
++
++ if (priv->res.h264_found)
++ *detected_video_codec = GST_WFD_VIDEO_H264;
++
++ if (priv->res.aac_found)
++ *detected_audio_codec = GST_WFD_AUDIO_AAC;
++
++ if (priv->res.ac3_found)
++ *detected_audio_codec = GST_WFD_AUDIO_AC3;
++
++ return GST_RTSP_OK;
++}
++
++gint
++gst_rtsp_media_factory_wfd_set_direct_streaming(GstRTSPMediaFactory * factory,
++ gint direct_streaming, gchar *filesrc)
++{
++ GstRTSPMediaFactoryWFD *_factory = GST_RTSP_MEDIA_FACTORY_WFD_CAST (factory);
++ linked = FALSE;
++
++ if (direct_streaming == 0) {
++ if (!_rtsp_media_factory_wfd_relink_pipeline(_factory)) {
++ GST_ERROR_OBJECT (factory, "Failed to relink pipeline");
++ return GST_RTSP_ERROR;
++ }
++
++ _rtsp_media_factory_wfd_destroy_direct_pipe ((void *)_factory);
++
++ GST_INFO_OBJECT (_factory, "Direct streaming bin removed");
++
++ return GST_RTSP_OK;
++ }
++
++ _factory->priv->direct_pipe = g_new0 (GstRTSPMediaWFDDirectPipelineData, 1);
++ _factory->priv->direct_pipe->uri = g_strdup(filesrc);
++
++ if (!_rtsp_media_factory_wfd_create_direct_pipeline(_factory)) {
++ GST_ERROR_OBJECT (_factory, "Direct pipeline creation failed");
++ return GST_RTSP_ERROR;
++ }
++
++ g_mutex_lock (&_factory->priv->direct_lock);
++ while (linked != TRUE) {
++ gint64 end_time = g_get_monotonic_time () + 5 * G_TIME_SPAN_SECOND;
++ if (!g_cond_wait_until (&_factory->priv->direct_cond, &_factory->priv->direct_lock, end_time)) {
++ g_mutex_unlock (&_factory->priv->direct_lock);
++ GST_ERROR_OBJECT (_factory, "Direct pipeline linking failed");
++ return GST_RTSP_ERROR;
++ }
++ }
++ g_mutex_unlock (&_factory->priv->direct_lock);
++
++ GST_INFO_OBJECT (_factory, "Direct streaming bin created");
++
++ return GST_RTSP_OK;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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 <gst/gst.h>
++
++#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);
++ void (*direct_stream_end) (GstRTSPMediaFactoryWFD * factory);
++
++ /*< private > */
++ gpointer _gst_reserved[GST_PADDING_LARGE];
++};
++
++GST_RTSP_SERVER_API
++GType gst_rtsp_media_factory_wfd_get_type (void);
++
++/* creating the factory */
++GST_RTSP_SERVER_API
++GstRTSPMediaFactoryWFD *gst_rtsp_media_factory_wfd_new (void);
++
++GST_RTSP_SERVER_API
++GstElement *gst_rtsp_media_factory_wfd_create_element (GstRTSPMediaFactoryWFD *
++ factory, const GstRTSPUrl * url);
++
++
++GST_RTSP_SERVER_API
++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);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_encoders (GstRTSPMediaFactoryWFD * factory,
++ gchar *video_encoder, gchar *audio_encoder_aac, gchar *audio_encoder_ac3);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_dump_ts (GstRTSPMediaFactoryWFD * factory,
++ gboolean dump_ts);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_negotiated_resolution (GstRTSPMediaFactory *factory,
++ guint32 width, guint32 height);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_audio_codec (GstRTSPMediaFactory *factory,
++ guint audio_codec);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_video_codec (GstRTSPMediaFactory * factory,
++ guint video_codec);
++
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_venc_bitrate (GstRTSPMediaFactory *factory,
++ gint bitrate);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_get_venc_bitrate (GstRTSPMediaFactory *factory,
++ gint *bitrate);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_get_config_bitrate (GstRTSPMediaFactory *factory,
++ guint32 *min, guint32 *max);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_factory_wfd_set_config_bitrate (GstRTSPMediaFactoryWFD *factory,
++ guint *config_bitrate);
++
++GST_RTSP_SERVER_API
++gint gst_rtsp_media_factory_wfd_uri_type_find(GstRTSPMediaFactory *factory,
++ gchar *filesrc, guint8 *detected_video_codec, guint8 *detected_audio_codec);
++
++GST_RTSP_SERVER_API
++gint gst_rtsp_media_factory_wfd_set_direct_streaming(GstRTSPMediaFactory *factory,
++ gint direct_streaming, gchar *filesrc);
++
++G_END_DECLS
++#endif /* __GST_RTSP_MEDIA_FACTORY_WFD_H__ */
--- /dev/null
-static void
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
+ *
+ * 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
+ * @short_description: The media pipeline
+ * @see_also: #GstRTSPMediaFactory, #GstRTSPStream, #GstRTSPSession,
+ * #GstRTSPSessionMedia
+ *
+ * a #GstRTSPMedia contains the complete GStreamer pipeline to manage the
+ * streaming to the clients. The actual data transfer is done by the
+ * #GstRTSPStream objects that are created and exposed by the #GstRTSPMedia.
+ *
+ * The #GstRTSPMedia is usually created from a #GstRTSPMediaFactory when the
+ * client does a DESCRIBE or SETUP of a resource.
+ *
+ * A media is created with gst_rtsp_media_new() that takes the element that will
+ * provide the streaming elements. For each of the streams, a new #GstRTSPStream
+ * object needs to be made with the gst_rtsp_media_create_stream() which takes
+ * the payloader element and the source pad that produces the RTP stream.
+ *
+ * The pipeline of the media is set to PAUSED with gst_rtsp_media_prepare(). The
+ * prepare method will add rtpbin and sinks and sources to send and receive RTP
+ * and RTCP packets from the clients. Each stream srcpad is connected to an
+ * input into the internal rtpbin.
+ *
+ * It is also possible to dynamically create #GstRTSPStream objects during the
+ * prepare phase. With gst_rtsp_media_get_status() you can check the status of
+ * the prepare phase.
+ *
+ * After the media is prepared, it is ready for streaming. It will usually be
+ * managed in a session with gst_rtsp_session_manage_media(). See
+ * #GstRTSPSession and #GstRTSPSessionMedia.
+ *
+ * The state of the media can be controlled with gst_rtsp_media_set_state ().
+ * Seeking can be done with gst_rtsp_media_seek(), or gst_rtsp_media_seek_full()
+ * or gst_rtsp_media_seek_trickmode() for finer control of the seek.
+ *
+ * With gst_rtsp_media_unprepare() the pipeline is stopped and shut down. When
+ * gst_rtsp_media_set_eos_shutdown() an EOS will be sent to the pipeline to
+ * cleanly shut down.
+ *
+ * With gst_rtsp_media_set_shared(), the media can be shared between multiple
+ * clients. With gst_rtsp_media_set_reusable() you can control if the pipeline
+ * can be prepared again after an unprepare.
+ *
+ * Last reviewed on 2013-07-11 (1.0.0)
+ */
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+
+ #include <stdio.h>
+ #include <string.h>
+ #include <stdlib.h>
+
+ #include <gst/app/gstappsrc.h>
+ #include <gst/app/gstappsink.h>
+
+ #include <gst/sdp/gstmikey.h>
+ #include <gst/rtp/gstrtppayloads.h>
+
+ #define AES_128_KEY_LEN 16
+ #define AES_256_KEY_LEN 32
+
+ #define HMAC_32_KEY_LEN 4
+ #define HMAC_80_KEY_LEN 10
+
+ #include "rtsp-media.h"
+ #include "rtsp-server-internal.h"
+
+ struct _GstRTSPMediaPrivate
+ {
+ GMutex lock;
+ GCond cond;
+
+ /* the global lock is used to lock the entire media. This is needed by callers
+ such as rtsp_client to protect the media when it is shared by many clients.
+ The lock prevents that concurrenting clients messes up media.
+ Typically the lock is taken in external API calls such as SETUP */
+ GMutex global_lock;
+
+ /* protected by lock */
+ GstRTSPPermissions *permissions;
+ gboolean shared;
+ gboolean suspend_mode;
+ gboolean reusable;
+ GstRTSPProfile profiles;
+ GstRTSPLowerTrans protocols;
+ gboolean reused;
+ gboolean eos_shutdown;
+ guint buffer_size;
+ gint dscp_qos;
+ GstRTSPAddressPool *pool;
+ gchar *multicast_iface;
+ guint max_mcast_ttl;
+ gboolean bind_mcast_address;
+ gboolean enable_rtcp;
+ gboolean blocked;
+ GstRTSPTransportMode transport_mode;
+ gboolean stop_on_disconnect;
+ guint blocking_msg_received;
+
+ GstElement *element;
+ GRecMutex state_lock; /* locking order: state lock, lock */
+ GPtrArray *streams; /* protected by lock */
+ GList *dynamic; /* protected by lock */
+ GstRTSPMediaStatus status; /* protected by lock */
+ gint prepare_count;
+ gint n_active;
+ gboolean complete;
+ gboolean finishing_unprepare;
+
+ /* the pipeline for the media */
+ GstElement *pipeline;
+ GSource *source;
+ GstRTSPThread *thread;
+ GList *pending_pipeline_elements;
+
+ gboolean time_provider;
+ GstNetTimeProvider *nettime;
+
+ gboolean is_live;
+ GstClockTimeDiff seekable;
+ gboolean buffering;
+ GstState target_state;
+
+ /* RTP session manager */
+ GstElement *rtpbin;
+
+ /* the range of media */
+ GstRTSPTimeRange range; /* protected by lock */
+ GstClockTime range_start;
+ GstClockTime range_stop;
+
+ GList *payloads; /* protected by lock */
+ GstClockTime rtx_time; /* protected by lock */
+ gboolean do_retransmission; /* protected by lock */
+ guint latency; /* protected by lock */
+ GstClock *clock; /* protected by lock */
+ gboolean do_rate_control; /* protected by lock */
+ GstRTSPPublishClockMode publish_clock_mode;
+
+ /* Dynamic element handling */
+ guint nb_dynamic_elements;
+ guint no_more_pads_pending;
+ gboolean expected_async_done;
+ };
+
+ #define DEFAULT_SHARED FALSE
+ #define DEFAULT_SUSPEND_MODE GST_RTSP_SUSPEND_MODE_NONE
+ #define DEFAULT_REUSABLE FALSE
+ #define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP
+ #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
+ GST_RTSP_LOWER_TRANS_TCP
+ #define DEFAULT_EOS_SHUTDOWN FALSE
+ #define DEFAULT_BUFFER_SIZE 0x80000
+ #define DEFAULT_DSCP_QOS (-1)
+ #define DEFAULT_TIME_PROVIDER FALSE
+ #define DEFAULT_LATENCY 200
+ #define DEFAULT_TRANSPORT_MODE GST_RTSP_TRANSPORT_MODE_PLAY
+ #define DEFAULT_STOP_ON_DISCONNECT TRUE
+ #define DEFAULT_MAX_MCAST_TTL 255
+ #define DEFAULT_BIND_MCAST_ADDRESS FALSE
+ #define DEFAULT_DO_RATE_CONTROL TRUE
+ #define DEFAULT_ENABLE_RTCP TRUE
+
+ #define DEFAULT_DO_RETRANSMISSION FALSE
+
+ /* define to dump received RTCP packets */
+ #undef DUMP_STATS
+
+ enum
+ {
+ PROP_0,
+ PROP_SHARED,
+ PROP_SUSPEND_MODE,
+ PROP_REUSABLE,
+ PROP_PROFILES,
+ PROP_PROTOCOLS,
+ PROP_EOS_SHUTDOWN,
+ PROP_BUFFER_SIZE,
+ PROP_ELEMENT,
+ PROP_TIME_PROVIDER,
+ PROP_LATENCY,
+ PROP_TRANSPORT_MODE,
+ PROP_STOP_ON_DISCONNECT,
+ PROP_CLOCK,
+ PROP_MAX_MCAST_TTL,
+ PROP_BIND_MCAST_ADDRESS,
+ PROP_DSCP_QOS,
+ PROP_LAST
+ };
+
+ enum
+ {
+ SIGNAL_NEW_STREAM,
+ SIGNAL_REMOVED_STREAM,
+ SIGNAL_PREPARED,
+ SIGNAL_UNPREPARED,
+ SIGNAL_TARGET_STATE,
+ SIGNAL_NEW_STATE,
++ SIGNAL_PREPARING,
++ SIGNAL_UNPREPARING,
+ SIGNAL_LAST
+ };
+
+ GST_DEBUG_CATEGORY_STATIC (rtsp_media_debug);
+ #define GST_CAT_DEFAULT rtsp_media_debug
+
+ static void gst_rtsp_media_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_media_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_media_finalize (GObject * obj);
+
+ static gboolean default_handle_message (GstRTSPMedia * media,
+ GstMessage * message);
+ static void finish_unprepare (GstRTSPMedia * media);
+ static gboolean default_prepare (GstRTSPMedia * media, GstRTSPThread * thread);
++static gboolean default_start_preroll (GstRTSPMedia * media);
+ static gboolean default_unprepare (GstRTSPMedia * media);
+ static gboolean default_suspend (GstRTSPMedia * media);
+ static gboolean default_unsuspend (GstRTSPMedia * media);
+ static gboolean default_convert_range (GstRTSPMedia * media,
+ GstRTSPTimeRange * range, GstRTSPRangeUnit unit);
+ static gboolean default_query_position (GstRTSPMedia * media,
+ gint64 * position);
+ static gboolean default_query_stop (GstRTSPMedia * media, gint64 * stop);
+ static GstElement *default_create_rtpbin (GstRTSPMedia * media);
+ static gboolean default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
+ GstSDPInfo * info);
+ static gboolean default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
++static gboolean default_start_prepare (GstRTSPMedia * media);
+
+ static gboolean wait_preroll (GstRTSPMedia * media);
+
+ static GstElement *find_payload_element (GstElement * payloader, GstPad * pad);
+
+ static guint gst_rtsp_media_signals[SIGNAL_LAST] = { 0 };
+
+ static gboolean check_complete (GstRTSPMedia * media);
+
+ #define C_ENUM(v) ((gint) v)
+
+ #define TRICKMODE_FLAGS (GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED)
+
+ GType
+ gst_rtsp_suspend_mode_get_type (void)
+ {
+ static gsize id = 0;
+ static const GEnumValue values[] = {
+ {C_ENUM (GST_RTSP_SUSPEND_MODE_NONE), "GST_RTSP_SUSPEND_MODE_NONE", "none"},
+ {C_ENUM (GST_RTSP_SUSPEND_MODE_PAUSE), "GST_RTSP_SUSPEND_MODE_PAUSE",
+ "pause"},
+ {C_ENUM (GST_RTSP_SUSPEND_MODE_RESET), "GST_RTSP_SUSPEND_MODE_RESET",
+ "reset"},
+ {0, NULL, NULL}
+ };
+
+ if (g_once_init_enter (&id)) {
+ GType tmp = g_enum_register_static ("GstRTSPSuspendMode", values);
+ g_once_init_leave (&id, tmp);
+ }
+ return (GType) id;
+ }
+
+ #define C_FLAGS(v) ((guint) v)
+
+ GType
+ gst_rtsp_transport_mode_get_type (void)
+ {
+ static gsize id = 0;
+ static const GFlagsValue values[] = {
+ {C_FLAGS (GST_RTSP_TRANSPORT_MODE_PLAY), "GST_RTSP_TRANSPORT_MODE_PLAY",
+ "play"},
+ {C_FLAGS (GST_RTSP_TRANSPORT_MODE_RECORD), "GST_RTSP_TRANSPORT_MODE_RECORD",
+ "record"},
+ {0, NULL, NULL}
+ };
+
+ if (g_once_init_enter (&id)) {
+ GType tmp = g_flags_register_static ("GstRTSPTransportMode", values);
+ g_once_init_leave (&id, tmp);
+ }
+ return (GType) id;
+ }
+
+ GType
+ gst_rtsp_publish_clock_mode_get_type (void)
+ {
+ static gsize id = 0;
+ static const GEnumValue values[] = {
+ {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_NONE),
+ "GST_RTSP_PUBLISH_CLOCK_MODE_NONE", "none"},
+ {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK),
+ "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK",
+ "clock"},
+ {C_ENUM (GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET),
+ "GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET",
+ "clock-and-offset"},
+ {0, NULL, NULL}
+ };
+
+ if (g_once_init_enter (&id)) {
+ GType tmp = g_enum_register_static ("GstRTSPPublishClockMode", values);
+ g_once_init_leave (&id, tmp);
+ }
+ return (GType) id;
+ }
+
+ G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPMedia, gst_rtsp_media, G_TYPE_OBJECT);
+
+ static void
+ gst_rtsp_media_class_init (GstRTSPMediaClass * klass)
+ {
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gst_rtsp_media_get_property;
+ gobject_class->set_property = gst_rtsp_media_set_property;
+ gobject_class->finalize = gst_rtsp_media_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_SHARED,
+ g_param_spec_boolean ("shared", "Shared",
+ "If this media pipeline can be shared", DEFAULT_SHARED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SUSPEND_MODE,
+ g_param_spec_enum ("suspend-mode", "Suspend Mode",
+ "How to suspend the media in PAUSED", GST_TYPE_RTSP_SUSPEND_MODE,
+ DEFAULT_SUSPEND_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_REUSABLE,
+ g_param_spec_boolean ("reusable", "Reusable",
+ "If this media pipeline can be reused after an unprepare",
+ DEFAULT_REUSABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PROFILES,
+ g_param_spec_flags ("profiles", "Profiles",
+ "Allowed transfer profiles", GST_TYPE_RTSP_PROFILE,
+ DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
+ g_param_spec_flags ("protocols", "Protocols",
+ "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
+ DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_EOS_SHUTDOWN,
+ g_param_spec_boolean ("eos-shutdown", "EOS Shutdown",
+ "Send an EOS event to the pipeline before unpreparing",
+ DEFAULT_EOS_SHUTDOWN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
+ g_param_spec_uint ("buffer-size", "Buffer Size",
+ "The kernel UDP buffer size to use", 0, G_MAXUINT,
+ DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_ELEMENT,
+ g_param_spec_object ("element", "The Element",
+ "The GstBin to use for streaming the media", GST_TYPE_ELEMENT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_TIME_PROVIDER,
+ g_param_spec_boolean ("time-provider", "Time Provider",
+ "Use a NetTimeProvider for clients",
+ DEFAULT_TIME_PROVIDER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_LATENCY,
+ g_param_spec_uint ("latency", "Latency",
+ "Latency used for receiving media in milliseconds", 0, G_MAXUINT,
+ DEFAULT_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TRANSPORT_MODE,
+ g_param_spec_flags ("transport-mode", "Transport Mode",
+ "If this media pipeline can be used for PLAY or RECORD",
+ GST_TYPE_RTSP_TRANSPORT_MODE, DEFAULT_TRANSPORT_MODE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_STOP_ON_DISCONNECT,
+ g_param_spec_boolean ("stop-on-disconnect", "Stop On Disconnect",
+ "If this media pipeline should be stopped "
+ "when a client disconnects without TEARDOWN",
+ DEFAULT_STOP_ON_DISCONNECT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_CLOCK,
+ g_param_spec_object ("clock", "Clock",
+ "Clock to be used by the media pipeline",
+ GST_TYPE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_MAX_MCAST_TTL,
+ g_param_spec_uint ("max-mcast-ttl", "Maximum multicast ttl",
+ "The maximum time-to-live value of outgoing multicast packets", 1,
+ 255, DEFAULT_MAX_MCAST_TTL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_BIND_MCAST_ADDRESS,
+ g_param_spec_boolean ("bind-mcast-address", "Bind mcast address",
+ "Whether the multicast sockets should be bound to multicast addresses "
+ "or INADDR_ANY",
+ DEFAULT_BIND_MCAST_ADDRESS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_DSCP_QOS,
+ g_param_spec_int ("dscp-qos", "DSCP QoS",
+ "The IP DSCP field to use for each related stream", -1, 63,
+ DEFAULT_DSCP_QOS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_rtsp_media_signals[SIGNAL_NEW_STREAM] =
+ g_signal_new ("new-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTSPMediaClass, new_stream), NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM);
+
+ gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM] =
+ g_signal_new ("removed-stream", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, removed_stream),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_STREAM);
+
+ gst_rtsp_media_signals[SIGNAL_PREPARED] =
+ g_signal_new ("prepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTSPMediaClass, prepared), NULL, NULL, NULL,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ gst_rtsp_media_signals[SIGNAL_UNPREPARED] =
+ g_signal_new ("unprepared", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTSPMediaClass, unprepared), NULL, NULL, NULL,
+ G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ gst_rtsp_media_signals[SIGNAL_TARGET_STATE] =
+ g_signal_new ("target-state", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPMediaClass, target_state),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT);
+
+ gst_rtsp_media_signals[SIGNAL_NEW_STATE] =
+ g_signal_new ("new-state", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTSPMediaClass, new_state), NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_INT);
+
++ gst_rtsp_media_signals[SIGNAL_PREPARING] =
++ g_signal_new ("preparing", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
++ G_STRUCT_OFFSET (GstRTSPMediaClass, preparing), NULL, NULL,
++ g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_RTSP_STREAM,
++ G_TYPE_UINT);
++
++ gst_rtsp_media_signals[SIGNAL_UNPREPARING] =
++ g_signal_new ("unpreparing", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
++ G_STRUCT_OFFSET (GstRTSPMediaClass, unpreparing), NULL, NULL,
++ g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_RTSP_STREAM,
++ G_TYPE_UINT);
++
+ GST_DEBUG_CATEGORY_INIT (rtsp_media_debug, "rtspmedia", 0, "GstRTSPMedia");
+
+ klass->handle_message = default_handle_message;
+ klass->prepare = default_prepare;
++ klass->start_preroll = default_start_preroll;
+ klass->unprepare = default_unprepare;
+ klass->suspend = default_suspend;
+ klass->unsuspend = default_unsuspend;
+ klass->convert_range = default_convert_range;
+ klass->query_position = default_query_position;
+ klass->query_stop = default_query_stop;
+ klass->create_rtpbin = default_create_rtpbin;
+ klass->setup_sdp = default_setup_sdp;
+ klass->handle_sdp = default_handle_sdp;
++ klass->start_prepare = default_start_prepare;
+ }
+
+ static void
+ gst_rtsp_media_init (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = gst_rtsp_media_get_instance_private (media);
+
+ media->priv = priv;
+
+ priv->streams = g_ptr_array_new_with_free_func (g_object_unref);
+ g_mutex_init (&priv->lock);
+ g_mutex_init (&priv->global_lock);
+ g_cond_init (&priv->cond);
+ g_rec_mutex_init (&priv->state_lock);
+
+ priv->shared = DEFAULT_SHARED;
+ priv->suspend_mode = DEFAULT_SUSPEND_MODE;
+ priv->reusable = DEFAULT_REUSABLE;
+ priv->profiles = DEFAULT_PROFILES;
+ priv->protocols = DEFAULT_PROTOCOLS;
+ priv->eos_shutdown = DEFAULT_EOS_SHUTDOWN;
+ priv->buffer_size = DEFAULT_BUFFER_SIZE;
+ priv->time_provider = DEFAULT_TIME_PROVIDER;
+ priv->transport_mode = DEFAULT_TRANSPORT_MODE;
+ priv->stop_on_disconnect = DEFAULT_STOP_ON_DISCONNECT;
+ priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
+ priv->do_retransmission = DEFAULT_DO_RETRANSMISSION;
+ priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL;
+ priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS;
+ priv->enable_rtcp = DEFAULT_ENABLE_RTCP;
+ priv->do_rate_control = DEFAULT_DO_RATE_CONTROL;
+ priv->dscp_qos = DEFAULT_DSCP_QOS;
+ priv->expected_async_done = FALSE;
+ priv->blocking_msg_received = 0;
+ }
+
+ static void
+ gst_rtsp_media_finalize (GObject * obj)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPMedia *media;
+
+ media = GST_RTSP_MEDIA (obj);
+ priv = media->priv;
+
+ GST_INFO ("finalize media %p", media);
+
+ if (priv->permissions)
+ gst_rtsp_permissions_unref (priv->permissions);
+
+ g_ptr_array_unref (priv->streams);
+
+ g_list_free_full (priv->dynamic, gst_object_unref);
+ g_list_free_full (priv->pending_pipeline_elements, gst_object_unref);
+
+ if (priv->pipeline)
+ gst_object_unref (priv->pipeline);
+ if (priv->nettime)
+ gst_object_unref (priv->nettime);
+ gst_object_unref (priv->element);
+ if (priv->pool)
+ g_object_unref (priv->pool);
+ if (priv->payloads)
+ g_list_free (priv->payloads);
+ if (priv->clock)
+ gst_object_unref (priv->clock);
+ g_free (priv->multicast_iface);
+ g_mutex_clear (&priv->lock);
+ g_mutex_clear (&priv->global_lock);
+ g_cond_clear (&priv->cond);
+ g_rec_mutex_clear (&priv->state_lock);
+
+ G_OBJECT_CLASS (gst_rtsp_media_parent_class)->finalize (obj);
+ }
+
+ static void
+ gst_rtsp_media_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPMedia *media = GST_RTSP_MEDIA (object);
+
+ switch (propid) {
+ case PROP_ELEMENT:
+ g_value_set_object (value, media->priv->element);
+ break;
+ case PROP_SHARED:
+ g_value_set_boolean (value, gst_rtsp_media_is_shared (media));
+ break;
+ case PROP_SUSPEND_MODE:
+ g_value_set_enum (value, gst_rtsp_media_get_suspend_mode (media));
+ break;
+ case PROP_REUSABLE:
+ g_value_set_boolean (value, gst_rtsp_media_is_reusable (media));
+ break;
+ case PROP_PROFILES:
+ g_value_set_flags (value, gst_rtsp_media_get_profiles (media));
+ break;
+ case PROP_PROTOCOLS:
+ g_value_set_flags (value, gst_rtsp_media_get_protocols (media));
+ break;
+ case PROP_EOS_SHUTDOWN:
+ g_value_set_boolean (value, gst_rtsp_media_is_eos_shutdown (media));
+ break;
+ case PROP_BUFFER_SIZE:
+ g_value_set_uint (value, gst_rtsp_media_get_buffer_size (media));
+ break;
+ case PROP_TIME_PROVIDER:
+ g_value_set_boolean (value, gst_rtsp_media_is_time_provider (media));
+ break;
+ case PROP_LATENCY:
+ g_value_set_uint (value, gst_rtsp_media_get_latency (media));
+ break;
+ case PROP_TRANSPORT_MODE:
+ g_value_set_flags (value, gst_rtsp_media_get_transport_mode (media));
+ break;
+ case PROP_STOP_ON_DISCONNECT:
+ g_value_set_boolean (value, gst_rtsp_media_is_stop_on_disconnect (media));
+ break;
+ case PROP_CLOCK:
+ g_value_take_object (value, gst_rtsp_media_get_clock (media));
+ break;
+ case PROP_MAX_MCAST_TTL:
+ g_value_set_uint (value, gst_rtsp_media_get_max_mcast_ttl (media));
+ break;
+ case PROP_BIND_MCAST_ADDRESS:
+ g_value_set_boolean (value, gst_rtsp_media_is_bind_mcast_address (media));
+ break;
+ case PROP_DSCP_QOS:
+ g_value_set_int (value, gst_rtsp_media_get_dscp_qos (media));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ static void
+ gst_rtsp_media_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPMedia *media = GST_RTSP_MEDIA (object);
+
+ switch (propid) {
+ case PROP_ELEMENT:
+ media->priv->element = g_value_get_object (value);
+ gst_object_ref_sink (media->priv->element);
+ break;
+ case PROP_SHARED:
+ gst_rtsp_media_set_shared (media, g_value_get_boolean (value));
+ break;
+ case PROP_SUSPEND_MODE:
+ gst_rtsp_media_set_suspend_mode (media, g_value_get_enum (value));
+ break;
+ case PROP_REUSABLE:
+ gst_rtsp_media_set_reusable (media, g_value_get_boolean (value));
+ break;
+ case PROP_PROFILES:
+ gst_rtsp_media_set_profiles (media, g_value_get_flags (value));
+ break;
+ case PROP_PROTOCOLS:
+ gst_rtsp_media_set_protocols (media, g_value_get_flags (value));
+ break;
+ case PROP_EOS_SHUTDOWN:
+ gst_rtsp_media_set_eos_shutdown (media, g_value_get_boolean (value));
+ break;
+ case PROP_BUFFER_SIZE:
+ gst_rtsp_media_set_buffer_size (media, g_value_get_uint (value));
+ break;
+ case PROP_TIME_PROVIDER:
+ gst_rtsp_media_use_time_provider (media, g_value_get_boolean (value));
+ break;
+ case PROP_LATENCY:
+ gst_rtsp_media_set_latency (media, g_value_get_uint (value));
+ break;
+ case PROP_TRANSPORT_MODE:
+ gst_rtsp_media_set_transport_mode (media, g_value_get_flags (value));
+ break;
+ case PROP_STOP_ON_DISCONNECT:
+ gst_rtsp_media_set_stop_on_disconnect (media,
+ g_value_get_boolean (value));
+ break;
+ case PROP_CLOCK:
+ gst_rtsp_media_set_clock (media, g_value_get_object (value));
+ break;
+ case PROP_MAX_MCAST_TTL:
+ gst_rtsp_media_set_max_mcast_ttl (media, g_value_get_uint (value));
+ break;
+ case PROP_BIND_MCAST_ADDRESS:
+ gst_rtsp_media_set_bind_mcast_address (media,
+ g_value_get_boolean (value));
+ break;
+ case PROP_DSCP_QOS:
+ gst_rtsp_media_set_dscp_qos (media, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ typedef struct
+ {
+ gint64 position;
+ gboolean complete_streams_only;
+ gboolean ret;
+ } DoQueryPositionData;
+
+ static void
+ do_query_position (GstRTSPStream * stream, DoQueryPositionData * data)
+ {
+ gint64 tmp;
+
+ if (!gst_rtsp_stream_is_sender (stream))
+ return;
+
+ if (data->complete_streams_only && !gst_rtsp_stream_is_complete (stream)) {
+ GST_DEBUG_OBJECT (stream, "stream not complete, do not query position");
+ return;
+ }
+
+ if (gst_rtsp_stream_query_position (stream, &tmp)) {
+ data->position = MIN (data->position, tmp);
+ data->ret = TRUE;
+ }
+
+ GST_INFO_OBJECT (stream, "media position: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (data->position));
+ }
+
+ static gboolean
+ default_query_position (GstRTSPMedia * media, gint64 * position)
+ {
+ GstRTSPMediaPrivate *priv;
+ DoQueryPositionData data;
+
+ priv = media->priv;
+
+ data.position = G_MAXINT64;
+ data.ret = FALSE;
+
+ /* if the media is complete, i.e. one or more streams have been configured
+ * with sinks, then we want to query the position on those streams only.
+ * a query on an incmplete stream may return a position that originates from
+ * an earlier preroll */
+ if (check_complete (media))
+ data.complete_streams_only = TRUE;
+ else
+ data.complete_streams_only = FALSE;
+
+ g_ptr_array_foreach (priv->streams, (GFunc) do_query_position, &data);
+
+ if (!data.ret)
+ *position = GST_CLOCK_TIME_NONE;
+ else
+ *position = data.position;
+
+ return data.ret;
+ }
+
+ typedef struct
+ {
+ gint64 stop;
+ gboolean ret;
+ } DoQueryStopData;
+
+ static void
+ do_query_stop (GstRTSPStream * stream, DoQueryStopData * data)
+ {
+ gint64 tmp = 0;
+
+ if (gst_rtsp_stream_query_stop (stream, &tmp)) {
+ data->stop = MAX (data->stop, tmp);
+ data->ret = TRUE;
+ }
+ }
+
+ static gboolean
+ default_query_stop (GstRTSPMedia * media, gint64 * stop)
+ {
+ GstRTSPMediaPrivate *priv;
+ DoQueryStopData data;
+
+ priv = media->priv;
+
+ data.stop = -1;
+ data.ret = FALSE;
+
+ g_ptr_array_foreach (priv->streams, (GFunc) do_query_stop, &data);
+
+ *stop = data.stop;
+
+ return data.ret;
+ }
+
+ static GstElement *
+ default_create_rtpbin (GstRTSPMedia * media)
+ {
+ GstElement *rtpbin;
+
+ rtpbin = gst_element_factory_make ("rtpbin", NULL);
+
+ return rtpbin;
+ }
+
+ /* Must be called with priv->lock */
+ static gboolean
+ is_receive_only (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gboolean receive_only = TRUE;
+ guint i;
+
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ if (gst_rtsp_stream_is_sender (stream) ||
+ !gst_rtsp_stream_is_receiver (stream)) {
+ receive_only = FALSE;
+ break;
+ }
+ }
+
+ return receive_only;
+ }
+
+ /* must be called with state lock */
+ static void
+ check_seekable (GstRTSPMedia * media)
+ {
+ GstQuery *query;
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ /* Update the seekable state of the pipeline in case it changed */
+ if (is_receive_only (media)) {
+ /* TODO: Seeking for "receive-only"? */
+ priv->seekable = -1;
+ } else {
+ guint i, n = priv->streams->len;
+
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ if (gst_rtsp_stream_get_publish_clock_mode (stream) ==
+ GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET) {
+ priv->seekable = -1;
+ g_mutex_unlock (&priv->lock);
+ return;
+ }
+ }
+ }
+
+ query = gst_query_new_seeking (GST_FORMAT_TIME);
+ if (gst_element_query (priv->pipeline, query)) {
+ GstFormat format;
+ gboolean seekable;
+ gint64 start, end;
+
+ gst_query_parse_seeking (query, &format, &seekable, &start, &end);
+ priv->seekable = seekable ? G_MAXINT64 : 0;
+ } else if (priv->streams->len) {
+ gboolean seekable = TRUE;
+ guint i, n = priv->streams->len;
+
+ GST_DEBUG_OBJECT (media, "Checking %d streams", n);
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ seekable &= gst_rtsp_stream_seekable (stream);
+ }
+ priv->seekable = seekable ? G_MAXINT64 : -1;
+ }
+
+ GST_DEBUG_OBJECT (media, "seekable:%" G_GINT64_FORMAT, priv->seekable);
+ g_mutex_unlock (&priv->lock);
+ gst_query_unref (query);
+ }
+
+ /* must be called with state lock */
+ static gboolean
+ check_complete (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ guint i, n = priv->streams->len;
+
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ if (gst_rtsp_stream_is_complete (stream))
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /* must be called with state lock and private lock */
+ static void
+ collect_media_stats (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gint64 position = 0, stop = -1;
+
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
+ priv->status != GST_RTSP_MEDIA_STATUS_PREPARING) {
+ return;
+ }
+
+ priv->range.unit = GST_RTSP_RANGE_NPT;
+
+ GST_INFO ("collect media stats");
+
+ if (priv->is_live) {
+ priv->range.min.type = GST_RTSP_TIME_NOW;
+ priv->range.min.seconds = -1;
+ priv->range_start = -1;
+ priv->range.max.type = GST_RTSP_TIME_END;
+ priv->range.max.seconds = -1;
+ priv->range_stop = -1;
+ } else {
+ GstRTSPMediaClass *klass;
+ gboolean ret;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ /* get the position */
+ ret = FALSE;
+ if (klass->query_position)
+ ret = klass->query_position (media, &position);
+
+ if (!ret) {
+ GST_INFO ("position query failed");
+ position = 0;
+ }
+
+ /* get the current segment stop */
+ ret = FALSE;
+ if (klass->query_stop)
+ ret = klass->query_stop (media, &stop);
+
+ if (!ret) {
+ GST_INFO ("stop query failed");
+ stop = -1;
+ }
+
+ GST_INFO ("stats: position %" GST_TIME_FORMAT ", stop %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (position), GST_TIME_ARGS (stop));
+
+ if (position == -1) {
+ priv->range.min.type = GST_RTSP_TIME_NOW;
+ priv->range.min.seconds = -1;
+ priv->range_start = -1;
+ } else {
+ priv->range.min.type = GST_RTSP_TIME_SECONDS;
+ priv->range.min.seconds = ((gdouble) position) / GST_SECOND;
+ priv->range_start = position;
+ }
+ if (stop == -1) {
+ priv->range.max.type = GST_RTSP_TIME_END;
+ priv->range.max.seconds = -1;
+ priv->range_stop = -1;
+ } else {
+ priv->range.max.type = GST_RTSP_TIME_SECONDS;
+ priv->range.max.seconds = ((gdouble) stop) / GST_SECOND;
+ priv->range_stop = stop;
+ }
+ g_mutex_unlock (&priv->lock);
+ check_seekable (media);
+ g_mutex_lock (&priv->lock);
+ }
+ }
+
+ /**
+ * gst_rtsp_media_new:
+ * @element: (transfer full): a #GstElement
+ *
+ * Create a new #GstRTSPMedia instance. @element is the bin element that
+ * provides the different streams. The #GstRTSPMedia object contains the
+ * element to produce RTP data for one or more related (audio/video/..)
+ * streams.
+ *
+ * Ownership is taken of @element.
+ *
+ * Returns: (transfer full): a new #GstRTSPMedia object.
+ */
+ GstRTSPMedia *
+ gst_rtsp_media_new (GstElement * element)
+ {
+ GstRTSPMedia *result;
+
+ g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
+
+ result = g_object_new (GST_TYPE_RTSP_MEDIA, "element", element, NULL);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_media_get_element:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the element that was used when constructing @media.
+ *
+ * Returns: (transfer full): a #GstElement. Unref after usage.
+ */
+ GstElement *
+ gst_rtsp_media_get_element (GstRTSPMedia * media)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ return gst_object_ref (media->priv->element);
+ }
+
+ /**
+ * gst_rtsp_media_take_pipeline:
+ * @media: a #GstRTSPMedia
+ * @pipeline: (transfer floating): a #GstPipeline
+ *
+ * Set @pipeline as the #GstPipeline for @media. Ownership is
+ * taken of @pipeline.
+ */
+ void
+ gst_rtsp_media_take_pipeline (GstRTSPMedia * media, GstPipeline * pipeline)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstElement *old;
+ GstNetTimeProvider *nettime;
+ GList *l;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+ g_return_if_fail (GST_IS_PIPELINE (pipeline));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ old = priv->pipeline;
+ priv->pipeline = gst_object_ref_sink (GST_ELEMENT_CAST (pipeline));
+ nettime = priv->nettime;
+ priv->nettime = NULL;
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ gst_object_unref (old);
+
+ if (nettime)
+ gst_object_unref (nettime);
+
+ gst_bin_add (GST_BIN_CAST (pipeline), priv->element);
+
+ for (l = priv->pending_pipeline_elements; l; l = l->next) {
+ gst_bin_add (GST_BIN_CAST (pipeline), l->data);
+ }
+ g_list_free (priv->pending_pipeline_elements);
+ priv->pending_pipeline_elements = NULL;
+ }
+
+ /**
+ * gst_rtsp_media_set_permissions:
+ * @media: a #GstRTSPMedia
+ * @permissions: (transfer none) (nullable): a #GstRTSPPermissions
+ *
+ * Set @permissions on @media.
+ */
+ void
+ gst_rtsp_media_set_permissions (GstRTSPMedia * media,
+ GstRTSPPermissions * permissions)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (priv->permissions)
+ gst_rtsp_permissions_unref (priv->permissions);
+ if ((priv->permissions = permissions))
+ gst_rtsp_permissions_ref (permissions);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_permissions:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the permissions object from @media.
+ *
+ * Returns: (transfer full) (nullable): a #GstRTSPPermissions object, unref after usage.
+ */
+ GstRTSPPermissions *
+ gst_rtsp_media_get_permissions (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPPermissions *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->permissions))
+ gst_rtsp_permissions_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_media_set_suspend_mode:
+ * @media: a #GstRTSPMedia
+ * @mode: the new #GstRTSPSuspendMode
+ *
+ * Control how @ media will be suspended after the SDP has been generated and
+ * after a PAUSE request has been performed.
+ *
+ * Media must be unprepared when setting the suspend mode.
+ */
+ void
+ gst_rtsp_media_set_suspend_mode (GstRTSPMedia * media, GstRTSPSuspendMode mode)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED)
+ goto was_prepared;
+ priv->suspend_mode = mode;
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return;
+
+ /* ERRORS */
+ was_prepared:
+ {
+ GST_WARNING ("media %p was prepared", media);
+ g_rec_mutex_unlock (&priv->state_lock);
+ }
+ }
+
+ /**
+ * gst_rtsp_media_get_suspend_mode:
+ * @media: a #GstRTSPMedia
+ *
+ * Get how @media will be suspended.
+ *
+ * Returns: #GstRTSPSuspendMode.
+ */
+ GstRTSPSuspendMode
+ gst_rtsp_media_get_suspend_mode (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPSuspendMode res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_SUSPEND_MODE_NONE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ res = priv->suspend_mode;
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_shared:
+ * @media: a #GstRTSPMedia
+ * @shared: the new value
+ *
+ * Set or unset if the pipeline for @media can be shared will multiple clients.
+ * When @shared is %TRUE, client requests for this media will share the media
+ * pipeline.
+ */
+ void
+ gst_rtsp_media_set_shared (GstRTSPMedia * media, gboolean shared)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->shared = shared;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_is_shared:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media can be shared between multiple clients.
+ *
+ * Returns: %TRUE if the media can be shared between clients.
+ */
+ gboolean
+ gst_rtsp_media_is_shared (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->shared;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_reusable:
+ * @media: a #GstRTSPMedia
+ * @reusable: the new value
+ *
+ * Set or unset if the pipeline for @media can be reused after the pipeline has
+ * been unprepared.
+ */
+ void
+ gst_rtsp_media_set_reusable (GstRTSPMedia * media, gboolean reusable)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->reusable = reusable;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_is_reusable:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media can be reused after an unprepare.
+ *
+ * Returns: %TRUE if the media can be reused
+ */
+ gboolean
+ gst_rtsp_media_is_reusable (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->reusable;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ static void
+ do_set_profiles (GstRTSPStream * stream, GstRTSPProfile * profiles)
+ {
+ gst_rtsp_stream_set_profiles (stream, *profiles);
+ }
+
+ /**
+ * gst_rtsp_media_set_profiles:
+ * @media: a #GstRTSPMedia
+ * @profiles: the new flags
+ *
+ * Configure the allowed lower transport for @media.
+ */
+ void
+ gst_rtsp_media_set_profiles (GstRTSPMedia * media, GstRTSPProfile profiles)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->profiles = profiles;
+ g_ptr_array_foreach (priv->streams, (GFunc) do_set_profiles, &profiles);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_profiles:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the allowed profiles of @media.
+ *
+ * Returns: a #GstRTSPProfile
+ */
+ GstRTSPProfile
+ gst_rtsp_media_get_profiles (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPProfile res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_RTSP_PROFILE_UNKNOWN);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->profiles;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ static void
+ do_set_protocols (GstRTSPStream * stream, GstRTSPLowerTrans * protocols)
+ {
+ gst_rtsp_stream_set_protocols (stream, *protocols);
+ }
+
+ /**
+ * gst_rtsp_media_set_protocols:
+ * @media: a #GstRTSPMedia
+ * @protocols: the new flags
+ *
+ * Configure the allowed lower transport for @media.
+ */
+ void
+ gst_rtsp_media_set_protocols (GstRTSPMedia * media, GstRTSPLowerTrans protocols)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->protocols = protocols;
+ g_ptr_array_foreach (priv->streams, (GFunc) do_set_protocols, &protocols);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_protocols:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the allowed protocols of @media.
+ *
+ * Returns: a #GstRTSPLowerTrans
+ */
+ GstRTSPLowerTrans
+ gst_rtsp_media_get_protocols (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPLowerTrans res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media),
+ GST_RTSP_LOWER_TRANS_UNKNOWN);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->protocols;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_eos_shutdown:
+ * @media: a #GstRTSPMedia
+ * @eos_shutdown: the new value
+ *
+ * Set or unset if an EOS event will be sent to the pipeline for @media before
+ * it is unprepared.
+ */
+ void
+ gst_rtsp_media_set_eos_shutdown (GstRTSPMedia * media, gboolean eos_shutdown)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->eos_shutdown = eos_shutdown;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_is_eos_shutdown:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media will send an EOS down the pipeline before
+ * unpreparing.
+ *
+ * Returns: %TRUE if the media will send EOS before unpreparing.
+ */
+ gboolean
+ gst_rtsp_media_is_eos_shutdown (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->eos_shutdown;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_buffer_size:
+ * @media: a #GstRTSPMedia
+ * @size: the new value
+ *
+ * Set the kernel UDP buffer size.
+ */
+ void
+ gst_rtsp_media_set_buffer_size (GstRTSPMedia * media, guint size)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ GST_LOG_OBJECT (media, "set buffer size %u", size);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->buffer_size = size;
+
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ gst_rtsp_stream_set_buffer_size (stream, size);
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_buffer_size:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the kernel UDP buffer size.
+ *
+ * Returns: the kernel UDP buffer size.
+ */
+ guint
+ gst_rtsp_media_get_buffer_size (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->buffer_size;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ static void
+ do_set_dscp_qos (GstRTSPStream * stream, gint * dscp_qos)
+ {
+ gst_rtsp_stream_set_dscp_qos (stream, *dscp_qos);
+ }
+
+ /**
+ * gst_rtsp_media_set_dscp_qos:
+ * @media: a #GstRTSPMedia
+ * @dscp_qos: a new dscp qos value (0-63, or -1 to disable)
+ *
+ * Configure the dscp qos of attached streams to @dscp_qos.
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_media_set_dscp_qos (GstRTSPMedia * media, gint dscp_qos)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ GST_LOG_OBJECT (media, "set DSCP QoS %d", dscp_qos);
+
+ if (dscp_qos < -1 || dscp_qos > 63) {
+ GST_WARNING_OBJECT (media, "trying to set illegal dscp qos %d", dscp_qos);
+ return;
+ }
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->dscp_qos = dscp_qos;
+ g_ptr_array_foreach (priv->streams, (GFunc) do_set_dscp_qos, &dscp_qos);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_dscp_qos:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the configured DSCP QoS of attached media.
+ *
+ * Returns: the DSCP QoS value of attached streams or -1 if disabled.
+ *
+ * Since: 1.18
+ */
+ gint
+ gst_rtsp_media_get_dscp_qos (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gint res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_unlock (&priv->lock);
+ res = priv->dscp_qos;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_stop_on_disconnect:
+ * @media: a #GstRTSPMedia
+ * @stop_on_disconnect: the new value
+ *
+ * Set or unset if the pipeline for @media should be stopped when a
+ * client disconnects without sending TEARDOWN.
+ */
+ void
+ gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia * media,
+ gboolean stop_on_disconnect)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->stop_on_disconnect = stop_on_disconnect;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_is_stop_on_disconnect:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media will be stopped when a client disconnects
+ * without sending TEARDOWN.
+ *
+ * Returns: %TRUE if the media will be stopped when a client disconnects
+ * without sending TEARDOWN.
+ */
+ gboolean
+ gst_rtsp_media_is_stop_on_disconnect (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), TRUE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->stop_on_disconnect;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_retransmission_time:
+ * @media: a #GstRTSPMedia
+ * @time: the new value
+ *
+ * Set the amount of time to store retransmission packets.
+ */
+ void
+ gst_rtsp_media_set_retransmission_time (GstRTSPMedia * media, GstClockTime time)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ GST_LOG_OBJECT (media, "set retransmission time %" G_GUINT64_FORMAT, time);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->rtx_time = time;
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ gst_rtsp_stream_set_retransmission_time (stream, time);
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_retransmission_time:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the amount of time to store retransmission data.
+ *
+ * Returns: the amount of time to store retransmission data.
+ */
+ GstClockTime
+ gst_rtsp_media_get_retransmission_time (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstClockTime res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->rtx_time;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_do_retransmission:
+ *
+ * Set whether retransmission requests will be sent
+ *
+ * Since: 1.16
+ */
+ void
+ gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media,
+ gboolean do_retransmission)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->do_retransmission = do_retransmission;
+
+ if (priv->rtpbin)
+ g_object_set (priv->rtpbin, "do-retransmission", do_retransmission, NULL);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_do_retransmission:
+ *
+ * Returns: Whether retransmission requests will be sent
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->do_retransmission;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ static void
+ update_stream_storage_size (GstRTSPMedia * media, GstRTSPStream * stream,
+ guint sessid)
+ {
+ GObject *storage = NULL;
+
+ g_signal_emit_by_name (G_OBJECT (media->priv->rtpbin), "get-storage",
+ sessid, &storage);
+
+ if (storage) {
+ guint64 size_time = 0;
+
+ if (!gst_rtsp_stream_is_tcp_receiver (stream))
+ size_time = (media->priv->latency + 50) * GST_MSECOND;
+
+ g_object_set (storage, "size-time", size_time, NULL);
+
+ g_object_unref (storage);
+ }
+ }
+
+ /**
+ * gst_rtsp_media_set_latency:
+ * @media: a #GstRTSPMedia
+ * @latency: latency in milliseconds
+ *
+ * Configure the latency used for receiving media.
+ */
+ void
+ gst_rtsp_media_set_latency (GstRTSPMedia * media, guint latency)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ GST_LOG_OBJECT (media, "set latency %ums", latency);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->latency = latency;
+ if (priv->rtpbin) {
+ g_object_set (priv->rtpbin, "latency", latency, NULL);
+
+ for (i = 0; i < media->priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
+ update_stream_storage_size (media, stream, i);
+ }
+ }
+
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_latency:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the latency that is used for receiving media.
+ *
+ * Returns: latency in milliseconds
+ */
+ guint
+ gst_rtsp_media_get_latency (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->latency;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_use_time_provider:
+ * @media: a #GstRTSPMedia
+ * @time_provider: if a #GstNetTimeProvider should be used
+ *
+ * Set @media to provide a #GstNetTimeProvider.
+ */
+ void
+ gst_rtsp_media_use_time_provider (GstRTSPMedia * media, gboolean time_provider)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->time_provider = time_provider;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_is_time_provider:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if @media can provide a #GstNetTimeProvider for its pipeline clock.
+ *
+ * Use gst_rtsp_media_get_time_provider() to get the network clock.
+ *
+ * Returns: %TRUE if @media can provide a #GstNetTimeProvider.
+ */
+ gboolean
+ gst_rtsp_media_is_time_provider (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->time_provider;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_clock:
+ * @media: a #GstRTSPMedia
+ * @clock: (nullable): #GstClock to be used
+ *
+ * Configure the clock used for the media.
+ */
+ void
+ gst_rtsp_media_set_clock (GstRTSPMedia * media, GstClock * clock)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+ g_return_if_fail (GST_IS_CLOCK (clock) || clock == NULL);
+
+ GST_LOG_OBJECT (media, "setting clock %" GST_PTR_FORMAT, clock);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (priv->clock)
+ gst_object_unref (priv->clock);
+ priv->clock = clock ? gst_object_ref (clock) : NULL;
+ if (priv->pipeline) {
+ if (clock)
+ gst_pipeline_use_clock (GST_PIPELINE_CAST (priv->pipeline), clock);
+ else
+ gst_pipeline_auto_clock (GST_PIPELINE_CAST (priv->pipeline));
+ }
+
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_set_publish_clock_mode:
+ * @media: a #GstRTSPMedia
+ * @mode: the clock publish mode
+ *
+ * Sets if and how the media clock should be published according to RFC7273.
+ *
+ * Since: 1.8
+ */
+ void
+ gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media,
+ GstRTSPPublishClockMode mode)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i, n;
+
+ priv = media->priv;
+ g_mutex_lock (&priv->lock);
+ priv->publish_clock_mode = mode;
+
+ n = priv->streams->len;
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ gst_rtsp_stream_set_publish_clock_mode (stream, mode);
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_publish_clock_mode:
+ * @media: a #GstRTSPMedia
+ *
+ * Gets if and how the media clock should be published according to RFC7273.
+ *
+ * Returns: The GstRTSPPublishClockMode
+ *
+ * Since: 1.8
+ */
+ GstRTSPPublishClockMode
+ gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPPublishClockMode ret;
+
+ priv = media->priv;
+ g_mutex_lock (&priv->lock);
+ ret = priv->publish_clock_mode;
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_media_set_address_pool:
+ * @media: a #GstRTSPMedia
+ * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
+ *
+ * configure @pool to be used as the address pool of @media.
+ */
+ void
+ gst_rtsp_media_set_address_pool (GstRTSPMedia * media,
+ GstRTSPAddressPool * pool)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPAddressPool *old;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ GST_LOG_OBJECT (media, "set address pool %p", pool);
+
+ g_mutex_lock (&priv->lock);
+ if ((old = priv->pool) != pool)
+ priv->pool = pool ? g_object_ref (pool) : NULL;
+ else
+ old = NULL;
+ g_ptr_array_foreach (priv->streams, (GFunc) gst_rtsp_stream_set_address_pool,
+ pool);
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_media_get_address_pool:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the #GstRTSPAddressPool used as the address pool of @media.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @media.
+ * g_object_unref() after usage.
+ */
+ GstRTSPAddressPool *
+ gst_rtsp_media_get_address_pool (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPAddressPool *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->pool))
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_media_set_multicast_iface:
+ * @media: a #GstRTSPMedia
+ * @multicast_iface: (transfer none) (nullable): a multicast interface name
+ *
+ * configure @multicast_iface to be used for @media.
+ */
+ void
+ gst_rtsp_media_set_multicast_iface (GstRTSPMedia * media,
+ const gchar * multicast_iface)
+ {
+ GstRTSPMediaPrivate *priv;
+ gchar *old;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ GST_LOG_OBJECT (media, "set multicast interface %s", multicast_iface);
+
+ g_mutex_lock (&priv->lock);
+ if ((old = priv->multicast_iface) != multicast_iface)
+ priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
+ else
+ old = NULL;
+ g_ptr_array_foreach (priv->streams,
+ (GFunc) gst_rtsp_stream_set_multicast_iface, (gchar *) multicast_iface);
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_free (old);
+ }
+
+ /**
+ * gst_rtsp_media_get_multicast_iface:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the multicast interface used for @media.
+ *
+ * Returns: (transfer full) (nullable): the multicast interface for @media.
+ * g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_media_get_multicast_iface (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->multicast_iface))
+ result = g_strdup (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_media_set_max_mcast_ttl:
+ * @media: a #GstRTSPMedia
+ * @ttl: the new multicast ttl value
+ *
+ * Set the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Returns: %TRUE if the requested ttl has been set successfully.
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_media_set_max_mcast_ttl (GstRTSPMedia * media, guint ttl)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ GST_LOG_OBJECT (media, "set max mcast ttl %u", ttl);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) {
+ GST_WARNING_OBJECT (media, "The reqested mcast TTL value is not valid.");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ priv->max_mcast_ttl = ttl;
+
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ gst_rtsp_stream_set_max_mcast_ttl (stream, ttl);
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_media_get_max_mcast_ttl:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Returns: the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Since: 1.16
+ */
+ guint
+ gst_rtsp_media_get_max_mcast_ttl (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->max_mcast_ttl;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_set_bind_mcast_address:
+ * @media: a #GstRTSPMedia
+ * @bind_mcast_addr: the new value
+ *
+ * Decide whether the multicast socket should be bound to a multicast address or
+ * INADDR_ANY.
+ *
+ * Since: 1.16
+ */
+ void
+ gst_rtsp_media_set_bind_mcast_address (GstRTSPMedia * media,
+ gboolean bind_mcast_addr)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->bind_mcast_address = bind_mcast_addr;
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ gst_rtsp_stream_set_bind_mcast_address (stream, bind_mcast_addr);
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_is_bind_mcast_address:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if multicast sockets are configured to be bound to multicast addresses.
+ *
+ * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses.
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_media_is_bind_mcast_address (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean result;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ result = priv->bind_mcast_address;
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ void
+ gst_rtsp_media_set_enable_rtcp (GstRTSPMedia * media, gboolean enable)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->enable_rtcp = enable;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ static GList *
+ _find_payload_types (GstRTSPMedia * media)
+ {
+ gint i, n;
+ GQueue queue = G_QUEUE_INIT;
+
+ n = media->priv->streams->len;
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (media->priv->streams, i);
+ guint pt = gst_rtsp_stream_get_pt (stream);
+
+ g_queue_push_tail (&queue, GUINT_TO_POINTER (pt));
+ }
+
+ return queue.head;
+ }
+
+ static guint
+ _next_available_pt (GList * payloads)
+ {
+ guint i;
+
+ for (i = 96; i <= 127; i++) {
+ GList *iter = g_list_find (payloads, GINT_TO_POINTER (i));
+ if (!iter)
+ return GPOINTER_TO_UINT (i);
+ }
+
+ return 0;
+ }
+
+ /**
+ * gst_rtsp_media_collect_streams:
+ * @media: a #GstRTSPMedia
+ *
+ * Find all payloader elements, they should be named pay\%d in the
+ * element of @media, and create #GstRTSPStreams for them.
+ *
+ * Collect all dynamic elements, named dynpay\%d, and add them to
+ * the list of dynamic elements.
+ *
+ * Find all depayloader elements, they should be named depay\%d in the
+ * element of @media, and create #GstRTSPStreams for them.
+ */
+ void
+ gst_rtsp_media_collect_streams (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstElement *element, *elem;
+ GstPad *pad;
+ gint i;
+ gboolean have_elem;
+ gboolean more_elem_remaining = TRUE;
+ GstRTSPTransportMode mode = 0;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+ element = priv->element;
+
+ have_elem = FALSE;
+ for (i = 0; more_elem_remaining; i++) {
+ gchar *name;
+
+ more_elem_remaining = FALSE;
+
+ name = g_strdup_printf ("pay%d", i);
+ if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
+ GstElement *pay;
+ GST_INFO ("found stream %d with payloader %p", i, elem);
+
+ /* take the pad of the payloader */
+ pad = gst_element_get_static_pad (elem, "src");
+
+ /* find the real payload element in case elem is a GstBin */
+ pay = find_payload_element (elem, pad);
+
+ /* create the stream */
+ if (pay == NULL) {
+ GST_WARNING ("could not find real payloader, using bin");
+ gst_rtsp_media_create_stream (media, elem, pad);
+ } else {
+ gst_rtsp_media_create_stream (media, pay, pad);
+ gst_object_unref (pay);
+ }
+
+ gst_object_unref (pad);
+ gst_object_unref (elem);
+
+ have_elem = TRUE;
+ more_elem_remaining = TRUE;
+ mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
+ }
+ g_free (name);
+
+ name = g_strdup_printf ("dynpay%d", i);
+ if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
+ /* a stream that will dynamically create pads to provide RTP packets */
+ GST_INFO ("found dynamic element %d, %p", i, elem);
+
+ g_mutex_lock (&priv->lock);
+ priv->dynamic = g_list_prepend (priv->dynamic, elem);
+ g_mutex_unlock (&priv->lock);
+
+ priv->nb_dynamic_elements++;
+
+ have_elem = TRUE;
+ more_elem_remaining = TRUE;
+ mode |= GST_RTSP_TRANSPORT_MODE_PLAY;
+ }
+ g_free (name);
+
+ name = g_strdup_printf ("depay%d", i);
+ if ((elem = gst_bin_get_by_name (GST_BIN (element), name))) {
+ GST_INFO ("found stream %d with depayloader %p", i, elem);
+
+ /* take the pad of the payloader */
+ pad = gst_element_get_static_pad (elem, "sink");
+ /* create the stream */
+ gst_rtsp_media_create_stream (media, elem, pad);
+ gst_object_unref (pad);
+ gst_object_unref (elem);
+
+ have_elem = TRUE;
+ more_elem_remaining = TRUE;
+ mode |= GST_RTSP_TRANSPORT_MODE_RECORD;
+ }
+ g_free (name);
+ }
+
+ if (have_elem) {
+ if (priv->transport_mode != mode)
+ GST_WARNING ("found different mode than expected (0x%02x != 0x%02d)",
+ priv->transport_mode, mode);
+ }
+ }
+
+ typedef struct
+ {
+ GstElement *appsink, *appsrc;
+ GstRTSPStream *stream;
+ } AppSinkSrcData;
+
+ static GstFlowReturn
+ appsink_new_sample (GstAppSink * appsink, gpointer user_data)
+ {
+ AppSinkSrcData *data = user_data;
+ GstSample *sample;
+ GstFlowReturn ret;
+
+ sample = gst_app_sink_pull_sample (appsink);
+ if (!sample)
+ return GST_FLOW_FLUSHING;
+
+
+ ret = gst_app_src_push_sample (GST_APP_SRC (data->appsrc), sample);
+ gst_sample_unref (sample);
+ return ret;
+ }
+
+ static GstAppSinkCallbacks appsink_callbacks = {
+ NULL,
+ NULL,
+ appsink_new_sample,
+ };
+
+ static GstPadProbeReturn
+ appsink_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+ {
+ AppSinkSrcData *data = user_data;
+
+ if (GST_IS_EVENT (info->data)
+ && GST_EVENT_TYPE (info->data) == GST_EVENT_LATENCY) {
+ GstClockTime min, max;
+
+ if (gst_base_sink_query_latency (GST_BASE_SINK (data->appsink), NULL, NULL,
+ &min, &max)) {
+ g_object_set (data->appsrc, "min-latency", min, "max-latency", max, NULL);
+ GST_DEBUG ("setting latency to min %" GST_TIME_FORMAT " max %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+ }
+ } else if (GST_IS_QUERY (info->data)) {
+ GstPad *srcpad = gst_element_get_static_pad (data->appsrc, "src");
+ if (gst_pad_peer_query (srcpad, GST_QUERY_CAST (info->data))) {
+ gst_object_unref (srcpad);
+ return GST_PAD_PROBE_HANDLED;
+ }
+ gst_object_unref (srcpad);
+ }
+
+ return GST_PAD_PROBE_OK;
+ }
+
+ static GstPadProbeReturn
+ appsrc_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+ {
+ AppSinkSrcData *data = user_data;
+
+ if (GST_IS_QUERY (info->data)) {
+ GstPad *sinkpad = gst_element_get_static_pad (data->appsink, "sink");
+ if (gst_pad_peer_query (sinkpad, GST_QUERY_CAST (info->data))) {
+ gst_object_unref (sinkpad);
+ return GST_PAD_PROBE_HANDLED;
+ }
+ gst_object_unref (sinkpad);
+ }
+
+ return GST_PAD_PROBE_OK;
+ }
+
+ /**
+ * gst_rtsp_media_create_stream:
+ * @media: a #GstRTSPMedia
+ * @payloader: a #GstElement
+ * @pad: a #GstPad
+ *
+ * Create a new stream in @media that provides RTP data on @pad.
+ * @pad should be a pad of an element inside @media->element.
+ *
+ * Returns: (transfer none): a new #GstRTSPStream that remains valid for as long
+ * as @media exists.
+ */
+ GstRTSPStream *
+ gst_rtsp_media_create_stream (GstRTSPMedia * media, GstElement * payloader,
+ GstPad * pad)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPStream *stream;
+ GstPad *streampad;
+ gchar *name;
+ gint idx;
+ AppSinkSrcData *data = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+ g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
+ g_return_val_if_fail (GST_IS_PAD (pad), NULL);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ idx = priv->streams->len;
+
+ GST_DEBUG ("media %p: creating stream with index %d and payloader %"
+ GST_PTR_FORMAT, media, idx, payloader);
+
+ if (GST_PAD_IS_SRC (pad))
+ name = g_strdup_printf ("src_%u", idx);
+ else
+ name = g_strdup_printf ("sink_%u", idx);
+
+ if ((GST_PAD_IS_SRC (pad) && priv->element->numsinkpads > 0) ||
+ (GST_PAD_IS_SINK (pad) && priv->element->numsrcpads > 0)) {
+ GstElement *appsink, *appsrc;
+ GstPad *sinkpad, *srcpad;
+
+ appsink = gst_element_factory_make ("appsink", NULL);
+ appsrc = gst_element_factory_make ("appsrc", NULL);
+
+ if (GST_PAD_IS_SINK (pad)) {
+ srcpad = gst_element_get_static_pad (appsrc, "src");
+
+ gst_bin_add (GST_BIN (priv->element), appsrc);
+
+ gst_pad_link (srcpad, pad);
+ gst_object_unref (srcpad);
+
+ streampad = gst_element_get_static_pad (appsink, "sink");
+
+ priv->pending_pipeline_elements =
+ g_list_prepend (priv->pending_pipeline_elements, appsink);
+ } else {
+ sinkpad = gst_element_get_static_pad (appsink, "sink");
+
+ gst_pad_link (pad, sinkpad);
+ gst_object_unref (sinkpad);
+
+ streampad = gst_element_get_static_pad (appsrc, "src");
+
+ priv->pending_pipeline_elements =
+ g_list_prepend (priv->pending_pipeline_elements, appsrc);
+ }
+
+ g_object_set (appsrc, "block", TRUE, "format", GST_FORMAT_TIME, "is-live",
+ TRUE, "emit-signals", FALSE, NULL);
+ g_object_set (appsink, "sync", FALSE, "async", FALSE, "emit-signals",
+ FALSE, "buffer-list", TRUE, NULL);
+
+ data = g_new0 (AppSinkSrcData, 1);
+ data->appsink = appsink;
+ data->appsrc = appsrc;
+
+ sinkpad = gst_element_get_static_pad (appsink, "sink");
+ gst_pad_add_probe (sinkpad,
+ GST_PAD_PROBE_TYPE_EVENT_UPSTREAM | GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+ appsink_pad_probe, data, NULL);
+ gst_object_unref (sinkpad);
+
+ srcpad = gst_element_get_static_pad (appsrc, "src");
+ gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
+ appsrc_pad_probe, data, NULL);
+ gst_object_unref (srcpad);
+
+ gst_app_sink_set_callbacks (GST_APP_SINK (appsink), &appsink_callbacks,
+ data, NULL);
+ g_object_set_data_full (G_OBJECT (streampad), "media-appsink-appsrc", data,
+ g_free);
+ } else {
+ streampad = gst_ghost_pad_new (name, pad);
+ gst_pad_set_active (streampad, TRUE);
+ gst_element_add_pad (priv->element, streampad);
+ }
+ g_free (name);
+
+ stream = gst_rtsp_stream_new (idx, payloader, streampad);
+ if (data)
+ data->stream = stream;
+ if (priv->pool)
+ gst_rtsp_stream_set_address_pool (stream, priv->pool);
+ gst_rtsp_stream_set_multicast_iface (stream, priv->multicast_iface);
+ gst_rtsp_stream_set_max_mcast_ttl (stream, priv->max_mcast_ttl);
+ gst_rtsp_stream_set_bind_mcast_address (stream, priv->bind_mcast_address);
+ gst_rtsp_stream_set_enable_rtcp (stream, priv->enable_rtcp);
+ gst_rtsp_stream_set_profiles (stream, priv->profiles);
+ gst_rtsp_stream_set_protocols (stream, priv->protocols);
+ gst_rtsp_stream_set_retransmission_time (stream, priv->rtx_time);
+ gst_rtsp_stream_set_buffer_size (stream, priv->buffer_size);
+ gst_rtsp_stream_set_publish_clock_mode (stream, priv->publish_clock_mode);
+ gst_rtsp_stream_set_rate_control (stream, priv->do_rate_control);
+
+ g_ptr_array_add (priv->streams, stream);
+
+ if (GST_PAD_IS_SRC (pad)) {
+ gint i, n;
+
+ if (priv->payloads)
+ g_list_free (priv->payloads);
+ priv->payloads = _find_payload_types (media);
+
+ n = priv->streams->len;
+ for (i = 0; i < n; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ guint rtx_pt = _next_available_pt (priv->payloads);
+
+ if (rtx_pt == 0) {
+ GST_WARNING ("Ran out of space of dynamic payload types");
+ break;
+ }
+
+ gst_rtsp_stream_set_retransmission_pt (stream, rtx_pt);
+
+ priv->payloads =
+ g_list_append (priv->payloads, GUINT_TO_POINTER (rtx_pt));
+ }
+ }
+ g_mutex_unlock (&priv->lock);
+
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STREAM], 0, stream,
+ NULL);
+
+ return stream;
+ }
+
+ static void
+ gst_rtsp_media_remove_stream (GstRTSPMedia * media, GstRTSPStream * stream)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstPad *srcpad;
+ AppSinkSrcData *data;
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ /* remove the ghostpad */
+ srcpad = gst_rtsp_stream_get_srcpad (stream);
+ data = g_object_get_data (G_OBJECT (srcpad), "media-appsink-appsrc");
+ if (data) {
+ if (GST_OBJECT_PARENT (data->appsrc) == GST_OBJECT_CAST (priv->pipeline))
+ gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsrc);
+ else if (GST_OBJECT_PARENT (data->appsrc) ==
+ GST_OBJECT_CAST (priv->element))
+ gst_bin_remove (GST_BIN_CAST (priv->element), data->appsrc);
+ if (GST_OBJECT_PARENT (data->appsink) == GST_OBJECT_CAST (priv->pipeline))
+ gst_bin_remove (GST_BIN_CAST (priv->pipeline), data->appsink);
+ else if (GST_OBJECT_PARENT (data->appsink) ==
+ GST_OBJECT_CAST (priv->element))
+ gst_bin_remove (GST_BIN_CAST (priv->element), data->appsink);
+ } else {
+ gst_element_remove_pad (priv->element, srcpad);
+ }
+ gst_object_unref (srcpad);
+ /* now remove the stream */
+ g_object_ref (stream);
+ g_ptr_array_remove (priv->streams, stream);
+ g_mutex_unlock (&priv->lock);
+
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_REMOVED_STREAM], 0,
+ stream, NULL);
+
+ g_object_unref (stream);
+ }
+
+ /**
+ * gst_rtsp_media_n_streams:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the number of streams in this media.
+ *
+ * Returns: The number of streams.
+ */
+ guint
+ gst_rtsp_media_n_streams (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), 0);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->streams->len;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_get_stream:
+ * @media: a #GstRTSPMedia
+ * @idx: the stream index
+ *
+ * Retrieve the stream with index @idx from @media.
+ *
+ * Returns: (nullable) (transfer none): the #GstRTSPStream at index
+ * @idx or %NULL when a stream with that index did not exist.
+ */
+ GstRTSPStream *
+ gst_rtsp_media_get_stream (GstRTSPMedia * media, guint idx)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPStream *res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (idx < priv->streams->len)
+ res = g_ptr_array_index (priv->streams, idx);
+ else
+ res = NULL;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_find_stream:
+ * @media: a #GstRTSPMedia
+ * @control: the control of the stream
+ *
+ * Find a stream in @media with @control as the control uri.
+ *
+ * Returns: (nullable) (transfer none): the #GstRTSPStream with
+ * control uri @control or %NULL when a stream with that control did
+ * not exist.
+ */
+ GstRTSPStream *
+ gst_rtsp_media_find_stream (GstRTSPMedia * media, const gchar * control)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPStream *res;
+ gint i;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+ g_return_val_if_fail (control != NULL, NULL);
+
+ priv = media->priv;
+
+ res = NULL;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *test;
+
+ test = g_ptr_array_index (priv->streams, i);
+ if (gst_rtsp_stream_has_control (test, control)) {
+ res = test;
+ break;
+ }
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /* called with state-lock */
+ static gboolean
+ default_convert_range (GstRTSPMedia * media, GstRTSPTimeRange * range,
+ GstRTSPRangeUnit unit)
+ {
+ return gst_rtsp_range_convert_units (range, unit);
+ }
+
+ /**
+ * gst_rtsp_media_get_range_string:
+ * @media: a #GstRTSPMedia
+ * @play: for the PLAY request
+ * @unit: the unit to use for the string
+ *
+ * Get the current range as a string. @media must be prepared with
+ * gst_rtsp_media_prepare ().
+ *
+ * Returns: (transfer full) (nullable): The range as a string, g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_media_get_range_string (GstRTSPMedia * media, gboolean play,
+ GstRTSPRangeUnit unit)
+ {
+ GstRTSPMediaClass *klass;
+ GstRTSPMediaPrivate *priv;
+ gchar *result;
+ GstRTSPTimeRange range;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+ g_return_val_if_fail (klass->convert_range != NULL, FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
+ priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
+ goto not_prepared;
+
+ /* Update the range value with current position/duration */
+ g_mutex_lock (&priv->lock);
+ collect_media_stats (media);
+
+ /* make copy */
+ range = priv->range;
+
+ if (!play && priv->n_active > 0) {
+ range.min.type = GST_RTSP_TIME_NOW;
+ range.min.seconds = -1;
+ }
+ g_mutex_unlock (&priv->lock);
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ if (!klass->convert_range (media, &range, unit))
+ goto conversion_failed;
+
+ result = gst_rtsp_range_to_string (&range);
+
+ return result;
+
+ /* ERRORS */
+ not_prepared:
+ {
+ GST_WARNING ("media %p was not prepared", media);
+ g_rec_mutex_unlock (&priv->state_lock);
+ return NULL;
+ }
+ conversion_failed:
+ {
+ GST_WARNING ("range conversion to unit %d failed", unit);
+ return NULL;
+ }
+ }
+
+ /**
+ * gst_rtsp_media_get_rates:
+ * @media: a #GstRTSPMedia
+ * @rate: (optional) (out caller-allocates): the rate of the current segment
+ * @applied_rate: (optional) (out caller-allocates): the applied_rate of the current segment
+ *
+ * Get the rate and applied_rate of the current segment.
+ *
+ * Returns: %FALSE if looking up the rate and applied rate failed. Otherwise
+ * %TRUE is returned and @rate and @applied_rate are set to the rate and
+ * applied_rate of the current segment.
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_media_get_rates (GstRTSPMedia * media, gdouble * rate,
+ gdouble * applied_rate)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPStream *stream;
+ gdouble save_rate, save_applied_rate;
+ gboolean result = TRUE;
+ gboolean first_stream = TRUE;
+ gint i;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ if (!rate && !applied_rate) {
+ GST_WARNING_OBJECT (media, "rate and applied_rate are both NULL");
+ return FALSE;
+ }
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ g_assert (priv->streams->len > 0);
+ for (i = 0; i < priv->streams->len; i++) {
+ stream = g_ptr_array_index (priv->streams, i);
+ if (gst_rtsp_stream_is_complete (stream)
+ && gst_rtsp_stream_is_sender (stream)) {
+ if (gst_rtsp_stream_get_rates (stream, rate, applied_rate)) {
+ if (first_stream) {
+ save_rate = *rate;
+ save_applied_rate = *applied_rate;
+ first_stream = FALSE;
+ } else {
+ if (save_rate != *rate || save_applied_rate != *applied_rate) {
+ /* diffrent rate or applied_rate, weird */
+ g_assert (FALSE);
+ result = FALSE;
+ break;
+ }
+ }
+ } else {
+ /* complete stream withot rate and applied_rate, weird */
+ g_assert (FALSE);
+ result = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (!result) {
+ GST_WARNING_OBJECT (media,
+ "failed to obtain consistent rate and applied_rate");
+ }
+
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ static void
+ stream_update_blocked (GstRTSPStream * stream, GstRTSPMedia * media)
+ {
+ gst_rtsp_stream_set_blocked (stream, media->priv->blocked);
+ }
+
+ static void
+ media_streams_set_blocked (GstRTSPMedia * media, gboolean blocked)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ GST_DEBUG ("media %p set blocked %d", media, blocked);
+ priv->blocked = blocked;
+ g_ptr_array_foreach (priv->streams, (GFunc) stream_update_blocked, media);
+
+ if (!blocked)
+ priv->blocking_msg_received = 0;
+ }
+
-static GstStateChangeReturn
-set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state)
++void
+ gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->status = status;
+ GST_DEBUG ("setting new status to %d", status);
+ g_cond_broadcast (&priv->cond);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_status:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the status of @media. When @media is busy preparing, this function waits
+ * until @media is prepared or in error.
+ *
+ * Returns: the status of @media.
+ */
+ GstRTSPMediaStatus
+ gst_rtsp_media_get_status (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPMediaStatus result;
+ gint64 end_time;
+
+ g_mutex_lock (&priv->lock);
+ end_time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND;
+ /* while we are preparing, wait */
+ while (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING) {
+ GST_DEBUG ("waiting for status change");
+ if (!g_cond_wait_until (&priv->cond, &priv->lock, end_time)) {
+ GST_DEBUG ("timeout, assuming error status");
+ priv->status = GST_RTSP_MEDIA_STATUS_ERROR;
+ }
+ }
+ /* could be success or error */
+ result = priv->status;
+ GST_DEBUG ("got status %d", result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_media_seek_trickmode:
+ * @media: a #GstRTSPMedia
+ * @range: (transfer none): a #GstRTSPTimeRange
+ * @flags: The minimal set of #GstSeekFlags to use
+ * @rate: the rate to use in the seek
+ * @trickmode_interval: The trickmode interval to use for KEY_UNITS trick mode
+ *
+ * Seek the pipeline of @media to @range with the given @flags and @rate,
+ * and @trickmode_interval.
+ * @media must be prepared with gst_rtsp_media_prepare().
+ * In order to perform the seek operation, the pipeline must contain all
+ * needed transport parts (transport sinks).
+ *
+ * Returns: %TRUE on success.
+ *
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_media_seek_trickmode (GstRTSPMedia * media,
+ GstRTSPTimeRange * range, GstSeekFlags flags, gdouble rate,
+ GstClockTime trickmode_interval)
+ {
+ GstRTSPMediaClass *klass;
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+ GstClockTime start, stop;
+ GstSeekType start_type, stop_type;
+ gint64 current_position;
+ gboolean force_seek;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ /* if there's a range then klass->convert_range must be set */
+ g_return_val_if_fail (range == NULL || klass->convert_range != NULL, FALSE);
+
+ GST_DEBUG ("flags=%x rate=%f", flags, rate);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
+ goto not_prepared;
+
+ /* check if the media pipeline is complete in order to perform a
+ * seek operation on it */
+ if (!check_complete (media))
+ goto not_complete;
+
+ /* Update the seekable state of the pipeline in case it changed */
+ check_seekable (media);
+
+ if (priv->seekable == 0) {
+ GST_FIXME_OBJECT (media, "Handle going back to 0 for none live"
+ " not seekable streams.");
+
+ goto not_seekable;
+ } else if (priv->seekable < 0) {
+ goto not_seekable;
+ }
+
+ start_type = stop_type = GST_SEEK_TYPE_NONE;
+ start = stop = GST_CLOCK_TIME_NONE;
+
+ /* if caller provided a range convert it to NPT format
+ * if no range provided the seek is assumed to be the same position but with
+ * e.g. the rate changed */
+ if (range != NULL) {
+ if (!klass->convert_range (media, range, GST_RTSP_RANGE_NPT))
+ goto not_supported;
+ gst_rtsp_range_get_times (range, &start, &stop);
+
+ GST_INFO ("got %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
+ GST_INFO ("current %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (priv->range_start), GST_TIME_ARGS (priv->range_stop));
+ }
+
+ current_position = -1;
+ if (klass->query_position)
+ klass->query_position (media, ¤t_position);
+ GST_INFO ("current media position %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (current_position));
+
+ if (start != GST_CLOCK_TIME_NONE)
+ start_type = GST_SEEK_TYPE_SET;
+
+ if (stop != GST_CLOCK_TIME_NONE)
+ stop_type = GST_SEEK_TYPE_SET;
+
+ /* we force a seek if any trickmode flag is set, or if the flush flag is set or
+ * the rate is non-standard, i.e. not 1.0 */
+ force_seek = (flags & TRICKMODE_FLAGS) || (flags & GST_SEEK_FLAG_FLUSH) ||
+ rate != 1.0;
+
+ if (start != GST_CLOCK_TIME_NONE || stop != GST_CLOCK_TIME_NONE || force_seek) {
+ GST_INFO ("seeking to %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
+
+ /* depends on the current playing state of the pipeline. We might need to
+ * queue this until we get EOS. */
+ flags |= GST_SEEK_FLAG_FLUSH;
+
+ /* if range start was not supplied we must continue from current position.
+ * but since we're doing a flushing seek, let us query the current position
+ * so we end up at exactly the same position after the seek. */
+ if (range == NULL || range->min.type == GST_RTSP_TIME_END) {
+ if (current_position == -1) {
+ GST_WARNING ("current position unknown");
+ } else {
+ GST_DEBUG ("doing accurate seek to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (current_position));
+ start = current_position;
+ start_type = GST_SEEK_TYPE_SET;
+ }
+ }
+
+ if (!force_seek &&
+ (start_type == GST_SEEK_TYPE_NONE || start == current_position) &&
+ (stop_type == GST_SEEK_TYPE_NONE || stop == priv->range_stop)) {
+ GST_DEBUG ("no position change, no flags set by caller, so not seeking");
+ res = TRUE;
+ } else {
+ GstEvent *seek_event;
+ gboolean unblock = FALSE;
+
+ /* Handle expected async-done before waiting on next async-done.
+ *
+ * Since the seek further down in code will cause a preroll and
+ * a async-done will be generated it's important to wait on async-done
+ * if that is expected. Otherwise there is the risk that the waiting
+ * for async-done after the seek is detecting the expected async-done
+ * instead of the one that corresponds to the seek. Then execution
+ * continue and act as if the pipeline is prerolled, but it's not.
+ *
+ * During wait_preroll message GST_MESSAGE_ASYNC_DONE will come
+ * and then the state will change from preparing to prepared */
+ if (priv->expected_async_done) {
+ GST_DEBUG (" expected to get async-done, waiting ");
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ /* wait until pipeline is prerolled */
+ if (!wait_preroll (media))
+ goto preroll_failed_expected_async_done;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ GST_DEBUG (" got expected async-done");
+ }
+
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+
+ if (rate < 0.0) {
+ GstClockTime temp_time = start;
+ GstSeekType temp_type = start_type;
+
+ start = stop;
+ start_type = stop_type;
+ stop = temp_time;
+ stop_type = temp_type;
+ }
+
+ seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
+ start, stop_type, stop);
+
+ gst_event_set_seek_trickmode_interval (seek_event, trickmode_interval);
+
+ if (!media->priv->blocked) {
+ /* Prevent a race condition with multiple streams,
+ * where one stream may have time to preroll before others
+ * have even started flushing, causing async-done to be
+ * posted too early.
+ */
+ media_streams_set_blocked (media, TRUE);
+ unblock = TRUE;
+ }
+
+ res = gst_element_send_event (priv->pipeline, seek_event);
+
+ if (unblock)
+ media_streams_set_blocked (media, FALSE);
+
+ /* and block for the seek to complete */
+ GST_INFO ("done seeking %d", res);
+ if (!res)
+ goto seek_failed;
+
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ /* wait until pipeline is prerolled again, this will also collect stats */
+ if (!wait_preroll (media))
+ goto preroll_failed;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ GST_INFO ("prerolled again");
+ }
+ } else {
+ GST_INFO ("no seek needed");
+ res = TRUE;
+ }
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return res;
+
+ /* ERRORS */
+ not_prepared:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("media %p is not prepared", media);
+ return FALSE;
+ }
+ not_complete:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("pipeline is not complete");
+ return FALSE;
+ }
+ not_seekable:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("pipeline is not seekable");
+ return FALSE;
+ }
+ not_supported:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_WARNING ("conversion to npt not supported");
+ return FALSE;
+ }
+ seek_failed:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("seeking failed");
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ return FALSE;
+ }
+ preroll_failed:
+ {
+ GST_WARNING ("failed to preroll after seek");
+ return FALSE;
+ }
+ preroll_failed_expected_async_done:
+ {
+ GST_WARNING ("failed to preroll");
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_media_seek_full:
+ * @media: a #GstRTSPMedia
+ * @range: (transfer none): a #GstRTSPTimeRange
+ * @flags: The minimal set of #GstSeekFlags to use
+ *
+ * Seek the pipeline of @media to @range with the given @flags.
+ * @media must be prepared with gst_rtsp_media_prepare().
+ *
+ * Returns: %TRUE on success.
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_media_seek_full (GstRTSPMedia * media, GstRTSPTimeRange * range,
+ GstSeekFlags flags)
+ {
+ return gst_rtsp_media_seek_trickmode (media, range, flags, 1.0, 0);
+ }
+
+ /**
+ * gst_rtsp_media_seek:
+ * @media: a #GstRTSPMedia
+ * @range: (transfer none): a #GstRTSPTimeRange
+ *
+ * Seek the pipeline of @media to @range. @media must be prepared with
+ * gst_rtsp_media_prepare().
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_seek (GstRTSPMedia * media, GstRTSPTimeRange * range)
+ {
+ return gst_rtsp_media_seek_trickmode (media, range, GST_SEEK_FLAG_NONE,
+ 1.0, 0);
+ }
+
+ static void
+ stream_collect_blocking (GstRTSPStream * stream, gboolean * blocked)
+ {
+ *blocked &= gst_rtsp_stream_is_blocking (stream);
+ }
+
+ static gboolean
+ media_streams_blocking (GstRTSPMedia * media)
+ {
+ gboolean blocking = TRUE;
+
+ g_ptr_array_foreach (media->priv->streams, (GFunc) stream_collect_blocking,
+ &blocking);
+
+ return blocking;
+ }
+
+ static GstStateChangeReturn
+ set_state (GstRTSPMedia * media, GstState state)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstStateChangeReturn ret;
+
+ GST_INFO ("set state to %s for media %p", gst_element_state_get_name (state),
+ media);
+ ret = gst_element_set_state (priv->pipeline, state);
+
++ {
++ gchar *filename = NULL;
++ filename = g_strdup_printf ("media_%s", gst_element_state_get_name (state));
++ GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (priv->pipeline),
++ GST_DEBUG_GRAPH_SHOW_ALL, filename);
++
++ g_free (filename);
++ }
++
+ return ret;
+ }
+
-start_preroll (GstRTSPMedia * media)
++GstStateChangeReturn
++gst_rtsp_media_set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstStateChangeReturn ret;
+
+ GST_INFO ("set target state to %s for media %p",
+ gst_element_state_get_name (state), media);
+ priv->target_state = state;
+
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_TARGET_STATE], 0,
+ priv->target_state, NULL);
+
+ if (do_state)
+ ret = set_state (media, state);
+ else
+ ret = GST_STATE_CHANGE_SUCCESS;
+
+ return ret;
+ }
+
+ static void
+ stream_collect_active_sender (GstRTSPStream * stream, guint * active_streams)
+ {
+ if (gst_rtsp_stream_is_complete (stream)
+ && gst_rtsp_stream_is_sender (stream))
+ (*active_streams)++;
+ }
+
+ static guint
+ nbr_active_sender_streams (GstRTSPMedia * media)
+ {
+ guint ret = 0;
+
+ g_ptr_array_foreach (media->priv->streams,
+ (GFunc) stream_collect_active_sender, &ret);
+
+ return ret;
+ }
+
+ /* called with state-lock */
+ /* called with state-lock */
+ static gboolean
+ default_handle_message (GstRTSPMedia * media, GstMessage * message)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstMessageType type;
+
+ type = GST_MESSAGE_TYPE (message);
+
+ switch (type) {
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old, new, pending;
+
+ if (GST_MESSAGE_SRC (message) != GST_OBJECT (priv->pipeline))
+ break;
+
+ gst_message_parse_state_changed (message, &old, &new, &pending);
+
+ GST_DEBUG ("%p: went from %s to %s (pending %s)", media,
+ gst_element_state_get_name (old), gst_element_state_get_name (new),
+ gst_element_state_get_name (pending));
+ if (priv->no_more_pads_pending == 0
+ && gst_rtsp_media_is_receive_only (media) && old == GST_STATE_READY
+ && new == GST_STATE_PAUSED) {
+ GST_INFO ("%p: went to PAUSED, prepared now", media);
+ g_mutex_lock (&priv->lock);
+ collect_media_stats (media);
+ g_mutex_unlock (&priv->lock);
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ }
+
+ break;
+ }
+ case GST_MESSAGE_BUFFERING:
+ {
+ gint percent;
+
+ gst_message_parse_buffering (message, &percent);
+
+ /* no state management needed for live pipelines */
+ if (priv->is_live)
+ break;
+
+ if (percent == 100) {
+ /* a 100% message means buffering is done */
+ priv->buffering = FALSE;
+ /* if the desired state is playing, go back */
+ if (priv->target_state == GST_STATE_PLAYING) {
+ GST_INFO ("Buffering done, setting pipeline to PLAYING");
+ set_state (media, GST_STATE_PLAYING);
+ } else {
+ GST_INFO ("Buffering done");
+ }
+ } else {
+ /* buffering busy */
+ if (priv->buffering == FALSE) {
+ if (priv->target_state == GST_STATE_PLAYING) {
+ /* we were not buffering but PLAYING, PAUSE the pipeline. */
+ GST_INFO ("Buffering, setting pipeline to PAUSED ...");
+ set_state (media, GST_STATE_PAUSED);
+ } else {
+ GST_INFO ("Buffering ...");
+ }
+ }
+ priv->buffering = TRUE;
+ }
+ break;
+ }
+ case GST_MESSAGE_LATENCY:
+ {
+ gst_bin_recalculate_latency (GST_BIN_CAST (priv->pipeline));
+ break;
+ }
+ case GST_MESSAGE_ERROR:
+ {
+ GError *gerror;
+ gchar *debug;
+
+ gst_message_parse_error (message, &gerror, &debug);
+ GST_WARNING ("%p: got error %s (%s)", media, gerror->message, debug);
+ g_error_free (gerror);
+ g_free (debug);
+
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ break;
+ }
+ case GST_MESSAGE_WARNING:
+ {
+ GError *gerror;
+ gchar *debug;
+
+ gst_message_parse_warning (message, &gerror, &debug);
+ GST_WARNING ("%p: got warning %s (%s)", media, gerror->message, debug);
+ g_error_free (gerror);
+ g_free (debug);
+ break;
+ }
+ case GST_MESSAGE_ELEMENT:
+ {
+ const GstStructure *s;
+
+ s = gst_message_get_structure (message);
+ if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) {
+ gboolean is_complete = FALSE;
+ guint n_active_sender_streams;
+ guint expected_nbr_blocking_msg;
+
+ /* to prevent problems when some streams are complete, some are not,
+ * we will ignore incomplete streams. When there are no complete
+ * streams (during DESCRIBE), we will listen to all streams. */
+
+ gst_structure_get_boolean (s, "is_complete", &is_complete);
+ n_active_sender_streams = nbr_active_sender_streams (media);
+ expected_nbr_blocking_msg = n_active_sender_streams;
+ GST_DEBUG_OBJECT (media, "media received blocking message,"
+ " n_active_sender_streams = %d, is_complete = %d",
+ n_active_sender_streams, is_complete);
+
+ if (n_active_sender_streams == 0 || is_complete)
+ priv->blocking_msg_received++;
+
+ if (n_active_sender_streams == 0)
+ expected_nbr_blocking_msg = priv->streams->len;
+
+ if (priv->blocked && media_streams_blocking (media) &&
+ priv->no_more_pads_pending == 0 &&
+ priv->blocking_msg_received == expected_nbr_blocking_msg) {
+ GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message), "media is blocking");
+ g_mutex_lock (&priv->lock);
+ collect_media_stats (media);
+ g_mutex_unlock (&priv->lock);
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+
+ priv->blocking_msg_received = 0;
+ }
+ }
+ break;
+ }
+ case GST_MESSAGE_STREAM_STATUS:
+ break;
+ case GST_MESSAGE_ASYNC_DONE:
+ if (priv->expected_async_done)
+ priv->expected_async_done = FALSE;
+ if (priv->complete) {
+ /* receive the final ASYNC_DONE, that is posted by the media pipeline
+ * after all the transport parts have been successfully added to
+ * the media streams. */
+ GST_DEBUG_OBJECT (media, "got async-done");
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ }
+ break;
+ case GST_MESSAGE_EOS:
+ GST_INFO ("%p: got EOS", media);
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARING) {
+ GST_DEBUG ("shutting down after EOS");
+ finish_unprepare (media);
+ }
+ break;
+ default:
+ GST_INFO ("%p: got message type %d (%s)", media, type,
+ gst_message_type_get_name (type));
+ break;
+ }
+ return TRUE;
+ }
+
+ static gboolean
+ bus_message (GstBus * bus, GstMessage * message, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPMediaClass *klass;
+ gboolean ret;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (klass->handle_message)
+ ret = klass->handle_message (media, message);
+ else
+ ret = FALSE;
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return ret;
+ }
+
+ static void
+ watch_destroyed (GstRTSPMedia * media)
+ {
+ GST_DEBUG_OBJECT (media, "source destroyed");
+ g_object_unref (media);
+ }
+
+ static gboolean
+ is_payloader (GstElement * element)
+ {
+ GstElementClass *eclass = GST_ELEMENT_GET_CLASS (element);
+ const gchar *klass;
+
+ klass = gst_element_class_get_metadata (eclass, GST_ELEMENT_METADATA_KLASS);
+ if (klass == NULL)
+ return FALSE;
+
+ if (strstr (klass, "Payloader") && strstr (klass, "RTP")) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ static GstElement *
+ find_payload_element (GstElement * payloader, GstPad * pad)
+ {
+ GstElement *pay = NULL;
+
+ if (GST_IS_BIN (payloader)) {
+ GstIterator *iter;
+ GValue item = { 0 };
+ gchar *pad_name, *payloader_name;
+ GstElement *element;
+
+ if ((element = gst_bin_get_by_name (GST_BIN (payloader), "pay"))) {
+ if (is_payloader (element))
+ return element;
+ gst_object_unref (element);
+ }
+
+ pad_name = gst_object_get_name (GST_OBJECT (pad));
+ payloader_name = g_strdup_printf ("pay_%s", pad_name);
+ g_free (pad_name);
+ if ((element = gst_bin_get_by_name (GST_BIN (payloader), payloader_name))) {
+ g_free (payloader_name);
+ if (is_payloader (element))
+ return element;
+ gst_object_unref (element);
+ } else {
+ g_free (payloader_name);
+ }
+
+ iter = gst_bin_iterate_recurse (GST_BIN (payloader));
+ while (gst_iterator_next (iter, &item) == GST_ITERATOR_OK) {
+ element = (GstElement *) g_value_get_object (&item);
+
+ if (is_payloader (element)) {
+ pay = gst_object_ref (element);
+ g_value_unset (&item);
+ break;
+ }
+ g_value_unset (&item);
+ }
+ gst_iterator_free (iter);
+ } else {
+ pay = g_object_ref (payloader);
+ }
+
+ return pay;
+ }
+
+ /* called from streaming threads */
+ static void
+ pad_added_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream;
+ GstElement *pay;
+
+ /* find the real payload element */
+ pay = find_payload_element (element, pad);
+ stream = gst_rtsp_media_create_stream (media, pay, pad);
+ gst_object_unref (pay);
+
+ GST_INFO ("pad added %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream);
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
+ goto not_preparing;
+
+ g_object_set_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream", stream);
+
+ /* join the element in the PAUSED state because this callback is
+ * called from the streaming thread and it is PAUSED */
+ if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
+ priv->rtpbin, GST_STATE_PAUSED)) {
+ GST_WARNING ("failed to join bin element");
+ }
+
+ if (priv->blocked)
+ gst_rtsp_stream_set_blocked (stream, TRUE);
+
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return;
+
+ /* ERRORS */
+ not_preparing:
+ {
+ gst_rtsp_media_remove_stream (media, stream);
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("ignore pad because we are not preparing");
+ return;
+ }
+ }
+
+ static void
+ pad_removed_cb (GstElement * element, GstPad * pad, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream;
+
+ stream = g_object_get_data (G_OBJECT (pad), "gst-rtsp-dynpad-stream");
+ if (stream == NULL)
+ return;
+
+ GST_INFO ("pad removed %s:%s, stream %p", GST_DEBUG_PAD_NAME (pad), stream);
+
+ g_rec_mutex_lock (&priv->state_lock);
+ gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ gst_rtsp_media_remove_stream (media, stream);
+ }
+
+ static void
+ no_more_pads_cb (GstElement * element, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ GST_INFO_OBJECT (element, "no more pads");
+ g_mutex_lock (&priv->lock);
+ priv->no_more_pads_pending--;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ typedef struct _DynPaySignalHandlers DynPaySignalHandlers;
+
+ struct _DynPaySignalHandlers
+ {
+ gulong pad_added_handler;
+ gulong pad_removed_handler;
+ gulong no_more_pads_handler;
+ };
+
+ static gboolean
- ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
++default_start_preroll (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstStateChangeReturn ret;
+
+ GST_INFO ("setting pipeline to PAUSED for media %p", media);
+
+ /* start blocked since it is possible that there are no sink elements yet */
+ media_streams_set_blocked (media, TRUE);
-start_prepare (GstRTSPMedia * media)
++ ret = gst_rtsp_media_set_target_state (media, GST_STATE_PAUSED, TRUE);
+
+ switch (ret) {
+ case GST_STATE_CHANGE_SUCCESS:
+ GST_INFO ("SUCCESS state change for media %p", media);
+ break;
+ case GST_STATE_CHANGE_ASYNC:
+ GST_INFO ("ASYNC state change for media %p", media);
+ break;
+ case GST_STATE_CHANGE_NO_PREROLL:
+ /* we need to go to PLAYING */
+ GST_INFO ("NO_PREROLL state change: live media %p", media);
+ /* FIXME we disable seeking for live streams for now. We should perform a
+ * seeking query in preroll instead */
+ priv->seekable = -1;
+ priv->is_live = TRUE;
+
+ ret = set_state (media, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto state_failed;
+ break;
+ case GST_STATE_CHANGE_FAILURE:
+ goto state_failed;
+ }
+
+ return TRUE;
+
+ state_failed:
+ {
+ GST_WARNING ("failed to preroll pipeline");
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ wait_preroll (GstRTSPMedia * media)
+ {
+ GstRTSPMediaStatus status;
+
+ GST_DEBUG ("wait to preroll pipeline");
+
+ /* wait until pipeline is prerolled */
+ status = gst_rtsp_media_get_status (media);
+ if (status == GST_RTSP_MEDIA_STATUS_ERROR)
+ goto preroll_failed;
+
+ return TRUE;
+
+ preroll_failed:
+ {
+ GST_WARNING ("failed to preroll pipeline");
+ return FALSE;
+ }
+ }
+
+ static GstElement *
+ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream = NULL;
+ guint i;
+ GstElement *res = NULL;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ stream = g_ptr_array_index (priv->streams, i);
+
+ if (sessid == gst_rtsp_stream_get_index (stream))
+ break;
+
+ stream = NULL;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (stream)
+ res = gst_rtsp_stream_request_aux_sender (stream, sessid);
+
+ return res;
+ }
+
+ static GstElement *
+ request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream = NULL;
+ guint i;
+ GstElement *res = NULL;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ stream = g_ptr_array_index (priv->streams, i);
+
+ if (sessid == gst_rtsp_stream_get_index (stream))
+ break;
+
+ stream = NULL;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (stream)
+ res = gst_rtsp_stream_request_aux_receiver (stream, sessid);
+
+ return res;
+ }
+
+ static GstElement *
+ request_fec_decoder (GstElement * rtpbin, guint sessid, GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPStream *stream = NULL;
+ guint i;
+ GstElement *res = NULL;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ stream = g_ptr_array_index (priv->streams, i);
+
+ if (sessid == gst_rtsp_stream_get_index (stream))
+ break;
+
+ stream = NULL;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (stream) {
+ res = gst_rtsp_stream_request_ulpfec_decoder (stream, rtpbin, sessid);
+ }
+
+ return res;
+ }
+
+ static gboolean
- } else if (!start_preroll (media)) {
++default_start_prepare (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
++ GstRTSPMediaClass *klass;
+ guint i;
+ GList *walk;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARING)
+ goto no_longer_preparing;
+
+ g_signal_connect (priv->rtpbin, "request-fec-decoder",
+ G_CALLBACK (request_fec_decoder), media);
+
++ klass = GST_RTSP_MEDIA_GET_CLASS (media);
++
+ /* link streams we already have, other streams might appear when we have
+ * dynamic elements */
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream;
+
+ stream = g_ptr_array_index (priv->streams, i);
+
+ if (priv->rtx_time > 0) {
+ /* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
+ g_signal_connect (priv->rtpbin, "request-aux-sender",
+ (GCallback) request_aux_sender, media);
+ }
+
+ if (priv->do_retransmission) {
+ g_signal_connect (priv->rtpbin, "request-aux-receiver",
+ (GCallback) request_aux_receiver, media);
+ }
+
+ if (!gst_rtsp_stream_join_bin (stream, GST_BIN (priv->pipeline),
+ priv->rtpbin, GST_STATE_NULL)) {
+ goto join_bin_failed;
+ }
++
++ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_PREPARING], 0, stream,
++ i, NULL);
+ }
+
+ if (priv->rtpbin)
+ g_object_set (priv->rtpbin, "do-retransmission", priv->do_retransmission,
+ "do-lost", TRUE, NULL);
+
+ for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
+ GstElement *elem = walk->data;
+ DynPaySignalHandlers *handlers = g_slice_new (DynPaySignalHandlers);
+
+ GST_INFO ("adding callbacks for dynamic element %p", elem);
+
+ handlers->pad_added_handler = g_signal_connect (elem, "pad-added",
+ (GCallback) pad_added_cb, media);
+ handlers->pad_removed_handler = g_signal_connect (elem, "pad-removed",
+ (GCallback) pad_removed_cb, media);
+ handlers->no_more_pads_handler = g_signal_connect (elem, "no-more-pads",
+ (GCallback) no_more_pads_cb, media);
+
+ g_object_set_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers", handlers);
+ }
+
+ if (priv->nb_dynamic_elements == 0 && gst_rtsp_media_is_receive_only (media)) {
+ /* If we are receive_only (RECORD), do not try to preroll, to avoid
+ * a second ASYNC state change failing */
+ priv->is_live = TRUE;
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
- g_source_set_callback (source, (GSourceFunc) start_prepare,
- g_object_ref (media), (GDestroyNotify) g_object_unref);
++ } else if (!klass->start_preroll (media)) {
+ goto preroll_failed;
+ }
+
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return FALSE;
+
+ no_longer_preparing:
+ {
+ GST_INFO ("media is no longer preparing");
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
+ join_bin_failed:
+ {
+ GST_WARNING ("failed to join bin element");
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
+ preroll_failed:
+ {
+ GST_WARNING ("failed to preroll pipeline");
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ default_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPMediaClass *klass;
+ GstBus *bus;
+ GMainContext *context;
+ GSource *source;
+
+ priv = media->priv;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ if (!klass->create_rtpbin)
+ goto no_create_rtpbin;
+
+ priv->rtpbin = klass->create_rtpbin (media);
+ if (priv->rtpbin != NULL) {
+ gboolean success = TRUE;
+
+ g_object_set (priv->rtpbin, "latency", priv->latency, NULL);
+
+ if (klass->setup_rtpbin)
+ success = klass->setup_rtpbin (media, priv->rtpbin);
+
+ if (success == FALSE) {
+ gst_object_unref (priv->rtpbin);
+ priv->rtpbin = NULL;
+ }
+ }
+ if (priv->rtpbin == NULL)
+ goto no_rtpbin;
+
+ priv->thread = thread;
+ context = (thread != NULL) ? (thread->context) : NULL;
+
+ bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline));
+
+ /* add the pipeline bus to our custom mainloop */
+ priv->source = gst_bus_create_watch (bus);
+ gst_object_unref (bus);
+
+ g_source_set_callback (priv->source, (GSourceFunc) bus_message,
+ g_object_ref (media), (GDestroyNotify) watch_destroyed);
+
+ g_source_attach (priv->source, context);
+
+ /* add stuff to the bin */
+ gst_bin_add (GST_BIN (priv->pipeline), priv->rtpbin);
+
+ /* do remainder in context */
+ source = g_idle_source_new ();
- set_target_state (media, GST_STATE_NULL, FALSE);
++ if (klass->start_prepare)
++ g_source_set_callback (source, (GSourceFunc) klass->start_prepare,
++ g_object_ref (media), (GDestroyNotify) g_object_unref);
+ g_source_attach (source, context);
+ g_source_unref (source);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_create_rtpbin:
+ {
+ GST_ERROR ("no create_rtpbin function");
+ g_critical ("no create_rtpbin vmethod function set");
+ return FALSE;
+ }
+ no_rtpbin:
+ {
+ GST_WARNING ("no rtpbin element");
+ g_warning ("failed to create element 'rtpbin', check your installation");
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_media_prepare:
+ * @media: a #GstRTSPMedia
+ * @thread: (transfer full) (allow-none): a #GstRTSPThread to run the
+ * bus handler or %NULL
+ *
+ * Prepare @media for streaming. This function will create the objects
+ * to manage the streaming. A pipeline must have been set on @media with
+ * gst_rtsp_media_take_pipeline().
+ *
+ * It will preroll the pipeline and collect vital information about the streams
+ * such as the duration.
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_prepare (GstRTSPMedia * media, GstRTSPThread * thread)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPMediaClass *klass;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ priv->prepare_count++;
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED ||
+ priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED)
+ goto was_prepared;
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING)
+ goto is_preparing;
+
+ if (priv->status != GST_RTSP_MEDIA_STATUS_UNPREPARED)
+ goto not_unprepared;
+
+ if (!priv->reusable && priv->reused)
+ goto is_reused;
+
+ GST_INFO ("preparing media %p", media);
+
+ /* reset some variables */
+ priv->is_live = FALSE;
+ priv->seekable = -1;
+ priv->buffering = FALSE;
+ priv->no_more_pads_pending = priv->nb_dynamic_elements;
+
+ /* we're preparing now */
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+ if (klass->prepare) {
+ if (!klass->prepare (media, thread))
+ goto prepare_failed;
+ }
+
+ wait_status:
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ /* now wait for all pads to be prerolled, FIXME, we should somehow be
+ * able to do this async so that we don't block the server thread. */
+ if (!wait_preroll (media))
+ goto preroll_failed;
+
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_PREPARED], 0, NULL);
+
+ GST_INFO ("object %p is prerolled", media);
+
+ return TRUE;
+
+ /* OK */
+ is_preparing:
+ {
+ /* we are not going to use the giving thread, so stop it. */
+ if (thread)
+ gst_rtsp_thread_stop (thread);
+ goto wait_status;
+ }
+ was_prepared:
+ {
+ GST_LOG ("media %p was prepared", media);
+ /* we are not going to use the giving thread, so stop it. */
+ if (thread)
+ gst_rtsp_thread_stop (thread);
+ g_rec_mutex_unlock (&priv->state_lock);
+ return TRUE;
+ }
+ /* ERRORS */
+ not_unprepared:
+ {
+ /* we are not going to use the giving thread, so stop it. */
+ if (thread)
+ gst_rtsp_thread_stop (thread);
+ GST_WARNING ("media %p was not unprepared", media);
+ priv->prepare_count--;
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
+ is_reused:
+ {
+ /* we are not going to use the giving thread, so stop it. */
+ if (thread)
+ gst_rtsp_thread_stop (thread);
+ priv->prepare_count--;
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_WARNING ("can not reuse media %p", media);
+ return FALSE;
+ }
+ prepare_failed:
+ {
+ /* we are not going to use the giving thread, so stop it. */
+ if (thread)
+ gst_rtsp_thread_stop (thread);
+ priv->prepare_count--;
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_ERROR ("failed to prepare media");
+ return FALSE;
+ }
+ preroll_failed:
+ {
+ GST_WARNING ("failed to preroll pipeline");
+ gst_rtsp_media_unprepare (media);
+ return FALSE;
+ }
+ }
+
+ /* must be called with state-lock */
+ static void
+ finish_unprepare (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gint i;
+ GList *walk;
+
+ if (priv->finishing_unprepare)
+ return;
+ priv->finishing_unprepare = TRUE;
+
+ GST_DEBUG ("shutting down");
+
+ /* release the lock on shutdown, otherwise pad_added_cb might try to
+ * acquire the lock and then we deadlock */
+ g_rec_mutex_unlock (&priv->state_lock);
+ set_state (media, GST_STATE_NULL);
+ g_rec_mutex_lock (&priv->state_lock);
+
+ media_streams_set_blocked (media, FALSE);
+
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream;
+
+ GST_INFO ("Removing elements of stream %d from pipeline", i);
+
+ stream = g_ptr_array_index (priv->streams, i);
+
+ gst_rtsp_stream_leave_bin (stream, GST_BIN (priv->pipeline), priv->rtpbin);
++
++ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_UNPREPARING], 0, stream,
++ i, NULL);
+ }
+
+ /* remove the pad signal handlers */
+ for (walk = priv->dynamic; walk; walk = g_list_next (walk)) {
+ GstElement *elem = walk->data;
+ DynPaySignalHandlers *handlers;
+
+ handlers =
+ g_object_steal_data (G_OBJECT (elem), "gst-rtsp-dynpay-handlers");
+ g_assert (handlers != NULL);
+
+ g_signal_handler_disconnect (G_OBJECT (elem), handlers->pad_added_handler);
+ g_signal_handler_disconnect (G_OBJECT (elem),
+ handlers->pad_removed_handler);
+ g_signal_handler_disconnect (G_OBJECT (elem),
+ handlers->no_more_pads_handler);
+
+ g_slice_free (DynPaySignalHandlers, handlers);
+ }
+
+ gst_bin_remove (GST_BIN (priv->pipeline), priv->rtpbin);
+ priv->rtpbin = NULL;
+
+ if (priv->nettime)
+ gst_object_unref (priv->nettime);
+ priv->nettime = NULL;
+
+ priv->reused = TRUE;
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARED);
+
+ /* when the media is not reusable, this will effectively unref the media and
+ * recreate it */
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_UNPREPARED], 0, NULL);
+
+ /* the source has the last ref to the media */
+ if (priv->source) {
+ GstBus *bus;
+
+ GST_DEBUG ("removing bus watch");
+ bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (priv->pipeline));
+ gst_bus_remove_watch (bus);
+ gst_object_unref (bus);
+
+ GST_DEBUG ("destroy source");
+ g_source_destroy (priv->source);
+ g_source_unref (priv->source);
+ priv->source = NULL;
+ }
+ if (priv->thread) {
+ GST_DEBUG ("stop thread");
+ gst_rtsp_thread_stop (priv->thread);
+ }
+
+ priv->finishing_unprepare = FALSE;
+ }
+
+ /* called with state-lock */
+ static gboolean
+ default_unprepare (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);
+
+ if (priv->eos_shutdown) {
+ GST_DEBUG ("sending EOS for shutdown");
+ /* ref so that we don't disappear */
+ gst_element_send_event (priv->pipeline, gst_event_new_eos ());
+ /* we need to go to playing again for the EOS to propagate, normally in this
+ * state, nothing is receiving data from us anymore so this is ok. */
+ set_state (media, GST_STATE_PLAYING);
+ } else {
+ finish_unprepare (media);
+ }
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_media_unprepare:
+ * @media: a #GstRTSPMedia
+ *
+ * Unprepare @media. After this call, the media should be prepared again before
+ * it can be used again. If the media is set to be non-reusable, a new instance
+ * must be created.
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_unprepare (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean success;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status == GST_RTSP_MEDIA_STATUS_UNPREPARED)
+ goto was_unprepared;
+
+ priv->prepare_count--;
+ if (priv->prepare_count > 0)
+ goto is_busy;
+
+ GST_INFO ("unprepare media %p", media);
- ret = set_target_state (media, GST_STATE_PAUSED, TRUE);
++ gst_rtsp_media_set_target_state (media, GST_STATE_NULL, FALSE);
+ success = TRUE;
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARED) {
+ GstRTSPMediaClass *klass;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+ if (klass->unprepare)
+ success = klass->unprepare (media);
+ } else {
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_UNPREPARING);
+ finish_unprepare (media);
+ }
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return success;
+
+ was_unprepared:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_INFO ("media %p was already unprepared", media);
+ return TRUE;
+ }
+ is_busy:
+ {
+ GST_INFO ("media %p still prepared %d times", media, priv->prepare_count);
+ g_rec_mutex_unlock (&priv->state_lock);
+ return TRUE;
+ }
+ }
+
+ /* should be called with state-lock */
+ static GstClock *
+ get_clock_unlocked (GstRTSPMedia * media)
+ {
+ if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED) {
+ GST_DEBUG_OBJECT (media, "media was not prepared");
+ return NULL;
+ }
+ return gst_pipeline_get_clock (GST_PIPELINE_CAST (media->priv->pipeline));
+ }
+
+ /**
+ * gst_rtsp_media_lock:
+ * @media: a #GstRTSPMedia
+ *
+ * Lock the entire media. This is needed by callers such as rtsp_client to
+ * protect the media when it is shared by many clients.
+ * The lock prevents that concurrent clients alters the shared media,
+ * while one client already is working with it.
+ * Typically the lock is taken in external RTSP API calls that uses shared media
+ * such as DESCRIBE, SETUP, ANNOUNCE, TEARDOWN, PLAY, PAUSE.
+ *
+ * As best practice take the lock as soon as the function get hold of a shared
+ * media object. Release the lock right before the function returns.
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_media_lock (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->global_lock);
+ }
+
+ /**
+ * gst_rtsp_media_unlock:
+ * @media: a #GstRTSPMedia
+ *
+ * Unlock the media.
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_media_unlock (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_unlock (&priv->global_lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_clock:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the clock that is used by the pipeline in @media.
+ *
+ * @media must be prepared before this method returns a valid clock object.
+ *
+ * Returns: (transfer full) (nullable): the #GstClock used by @media. unref after usage.
+ */
+ GstClock *
+ gst_rtsp_media_get_clock (GstRTSPMedia * media)
+ {
+ GstClock *clock;
+ GstRTSPMediaPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ clock = get_clock_unlocked (media);
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return clock;
+ }
+
+ /**
+ * gst_rtsp_media_get_base_time:
+ * @media: a #GstRTSPMedia
+ *
+ * Get the base_time that is used by the pipeline in @media.
+ *
+ * @media must be prepared before this method returns a valid base_time.
+ *
+ * Returns: the base_time used by @media.
+ */
+ GstClockTime
+ gst_rtsp_media_get_base_time (GstRTSPMedia * media)
+ {
+ GstClockTime result;
+ GstRTSPMediaPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), GST_CLOCK_TIME_NONE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (media->priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
+ goto not_prepared;
+
+ result = gst_element_get_base_time (media->priv->pipeline);
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return result;
+
+ /* ERRORS */
+ not_prepared:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_DEBUG_OBJECT (media, "media was not prepared");
+ return GST_CLOCK_TIME_NONE;
+ }
+ }
+
+ /**
+ * gst_rtsp_media_get_time_provider:
+ * @media: a #GstRTSPMedia
+ * @address: (allow-none): an address or %NULL
+ * @port: a port or 0
+ *
+ * Get the #GstNetTimeProvider for the clock used by @media. The time provider
+ * will listen on @address and @port for client time requests.
+ *
+ * Returns: (transfer full): the #GstNetTimeProvider of @media.
+ */
+ GstNetTimeProvider *
+ gst_rtsp_media_get_time_provider (GstRTSPMedia * media, const gchar * address,
+ guint16 port)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstNetTimeProvider *provider = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->time_provider) {
+ if ((provider = priv->nettime) == NULL) {
+ GstClock *clock;
+
+ if (priv->time_provider && (clock = get_clock_unlocked (media))) {
+ provider = gst_net_time_provider_new (clock, address, port);
+ gst_object_unref (clock);
+
+ priv->nettime = provider;
+ }
+ }
+ }
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ if (provider)
+ gst_object_ref (provider);
+
+ return provider;
+ }
+
+ static gboolean
+ default_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp, GstSDPInfo * info)
+ {
+ return gst_rtsp_sdp_from_media (sdp, info, media);
+ }
+
+ /**
+ * gst_rtsp_media_setup_sdp:
+ * @media: a #GstRTSPMedia
+ * @sdp: (transfer none): a #GstSDPMessage
+ * @info: (transfer none): a #GstSDPInfo
+ *
+ * Add @media specific info to @sdp. @info is used to configure the connection
+ * information in the SDP.
+ *
+ * Returns: TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
+ GstSDPInfo * info)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPMediaClass *klass;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ g_return_val_if_fail (sdp != NULL, FALSE);
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ if (!klass->setup_sdp)
+ goto no_setup_sdp;
+
+ res = klass->setup_sdp (media, sdp, info);
+
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return res;
+
+ /* ERRORS */
+ no_setup_sdp:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_ERROR ("no setup_sdp function");
+ g_critical ("no setup_sdp vmethod function set");
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ default_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gint i, medias_len;
+
+ medias_len = gst_sdp_message_medias_len (sdp);
+ if (medias_len != priv->streams->len) {
+ GST_ERROR ("%p: Media has more or less streams than SDP (%d /= %d)", media,
+ priv->streams->len, medias_len);
+ return FALSE;
+ }
+
+ for (i = 0; i < medias_len; i++) {
+ const gchar *proto;
+ const GstSDPMedia *sdp_media = gst_sdp_message_get_media (sdp, i);
+ GstRTSPStream *stream;
+ gint j, formats_len;
+ const gchar *control;
+ GstRTSPProfile profile, profiles;
+
+ stream = g_ptr_array_index (priv->streams, i);
+
+ /* TODO: Should we do something with the other SDP information? */
+
+ /* get proto */
+ proto = gst_sdp_media_get_proto (sdp_media);
+ if (proto == NULL) {
+ GST_ERROR ("%p: SDP media %d has no proto", media, i);
+ return FALSE;
+ }
+
+ if (g_str_equal (proto, "RTP/AVP")) {
+ profile = GST_RTSP_PROFILE_AVP;
+ } else if (g_str_equal (proto, "RTP/SAVP")) {
+ profile = GST_RTSP_PROFILE_SAVP;
+ } else if (g_str_equal (proto, "RTP/AVPF")) {
+ profile = GST_RTSP_PROFILE_AVPF;
+ } else if (g_str_equal (proto, "RTP/SAVPF")) {
+ profile = GST_RTSP_PROFILE_SAVPF;
+ } else {
+ GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
+ return FALSE;
+ }
+
+ profiles = gst_rtsp_stream_get_profiles (stream);
+ if ((profiles & profile) == 0) {
+ GST_ERROR ("%p: unsupported profile '%s' for stream %d", media, proto, i);
+ return FALSE;
+ }
+
+ formats_len = gst_sdp_media_formats_len (sdp_media);
+ for (j = 0; j < formats_len; j++) {
+ gint pt;
+ GstCaps *caps;
+ GstStructure *s;
+
+ pt = atoi (gst_sdp_media_get_format (sdp_media, j));
+
+ GST_DEBUG (" looking at %d pt: %d", j, pt);
+
+ /* convert caps */
+ caps = gst_sdp_media_get_caps_from_media (sdp_media, pt);
+ if (caps == NULL) {
+ GST_WARNING (" skipping pt %d without caps", pt);
+ continue;
+ }
+
+ /* do some tweaks */
+ GST_DEBUG ("mapping sdp session level attributes to caps");
+ gst_sdp_message_attributes_to_caps (sdp, caps);
+ GST_DEBUG ("mapping sdp media level attributes to caps");
+ gst_sdp_media_attributes_to_caps (sdp_media, caps);
+
+ s = gst_caps_get_structure (caps, 0);
+ gst_structure_set_name (s, "application/x-rtp");
+
+ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
+ gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL);
+
+ gst_rtsp_stream_set_pt_map (stream, pt, caps);
+ gst_caps_unref (caps);
+ }
+
+ control = gst_sdp_media_get_attribute_val (sdp_media, "control");
+ if (control)
+ gst_rtsp_stream_set_control (stream, control);
+
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_media_handle_sdp:
+ * @media: a #GstRTSPMedia
+ * @sdp: (transfer none): a #GstSDPMessage
+ *
+ * Configure an SDP on @media for receiving streams
+ *
+ * Returns: TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPMediaClass *klass;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ g_return_val_if_fail (sdp != NULL, FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ if (!klass->handle_sdp)
+ goto no_handle_sdp;
+
+ res = klass->handle_sdp (media, sdp);
+
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return res;
+
+ /* ERRORS */
+ no_handle_sdp:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_ERROR ("no handle_sdp function");
+ g_critical ("no handle_sdp vmethod function set");
+ return FALSE;
+ }
+ }
+
+ static void
+ do_set_seqnum (GstRTSPStream * stream)
+ {
+ guint16 seq_num;
+
+ if (gst_rtsp_stream_is_sender (stream)) {
+ seq_num = gst_rtsp_stream_get_current_seqnum (stream);
+ gst_rtsp_stream_set_seqnum_offset (stream, seq_num + 1);
+ }
+ }
+
+ /* call with state_lock */
+ static gboolean
+ default_suspend (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;
+
+ switch (priv->suspend_mode) {
+ case GST_RTSP_SUSPEND_MODE_NONE:
+ GST_DEBUG ("media %p no suspend", media);
+ break;
+ case GST_RTSP_SUSPEND_MODE_PAUSE:
+ GST_DEBUG ("media %p suspend to PAUSED", media);
- ret = set_target_state (media, GST_STATE_NULL, TRUE);
++ ret = gst_rtsp_media_set_target_state (media, GST_STATE_PAUSED, TRUE);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto state_failed;
+ break;
+ case GST_RTSP_SUSPEND_MODE_RESET:
+ GST_DEBUG ("media %p suspend to NULL", media);
- if (!start_preroll (media))
- goto start_failed;
++ 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
+ * monotonic, we need to preserve the sequence number
+ * after pause. (otherwise going from pause to play, which
+ * is actually from NULL to PLAY will create a new sequence
+ * number. */
+ g_ptr_array_foreach (priv->streams, (GFunc) do_set_seqnum, NULL);
+ break;
+ default:
+ break;
+ }
+
+ /* If we use any suspend mode that changes the state then we must update
+ * expected_async_done, since we might not be doing an asyncronous state
+ * change anymore. */
+ if (ret != GST_STATE_CHANGE_FAILURE && ret != GST_STATE_CHANGE_ASYNC)
+ priv->expected_async_done = FALSE;
+
+ return TRUE;
+
+ /* ERRORS */
+ state_failed:
+ {
+ GST_WARNING ("failed changing pipeline's state for media %p", media);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_media_suspend:
+ * @media: a #GstRTSPMedia
+ *
+ * Suspend @media. The state of the pipeline managed by @media is set to
+ * GST_STATE_NULL but all streams are kept. @media can be prepared again
+ * with gst_rtsp_media_unsuspend()
+ *
+ * @media must be prepared with gst_rtsp_media_prepare();
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_suspend (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPMediaClass *klass;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ GST_FIXME ("suspend for dynamic pipelines needs fixing");
+
+ /* this typically can happen for shared media. */
+ if (priv->prepare_count > 1 &&
+ priv->status == GST_RTSP_MEDIA_STATUS_SUSPENDED) {
+ goto done;
+ } else if (priv->prepare_count > 1) {
+ goto prepared_by_other_client;
+ }
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED)
+ goto not_prepared;
+
+ /* don't attempt to suspend when something is busy */
+ if (priv->n_active > 0)
+ goto done;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+ if (klass->suspend) {
+ if (!klass->suspend (media))
+ goto suspend_failed;
+ }
+
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_SUSPENDED);
+ done:
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ prepared_by_other_client:
+ {
+ GST_WARNING ("media %p was prepared by other client", media);
+ return FALSE;
+ }
+ not_prepared:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_WARNING ("media %p was not prepared", media);
+ return FALSE;
+ }
+ suspend_failed:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ GST_WARNING ("failed to suspend media %p", media);
+ return FALSE;
+ }
+ }
+
+ /* call with state_lock */
+ static gboolean
+ default_unsuspend (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gboolean preroll_ok;
++ GstRTSPMediaClass *klass;
++
++ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+
+ switch (priv->suspend_mode) {
+ case GST_RTSP_SUSPEND_MODE_NONE:
+ if (gst_rtsp_media_is_receive_only (media))
+ break;
+ if (media_streams_blocking (media)) {
+ g_rec_mutex_unlock (&priv->state_lock);
+ if (gst_rtsp_media_get_status (media) == GST_RTSP_MEDIA_STATUS_ERROR) {
+ g_rec_mutex_lock (&priv->state_lock);
+ goto preroll_failed;
+ }
+ g_rec_mutex_lock (&priv->state_lock);
+ }
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ break;
+ case GST_RTSP_SUSPEND_MODE_PAUSE:
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARED);
+ break;
+ case GST_RTSP_SUSPEND_MODE_RESET:
+ {
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING);
+ /* at this point the media pipeline has been updated and contain all
+ * specific transport parts: all active streams contain at least one sink
+ * element and it's safe to unblock all blocked streams */
+ media_streams_set_blocked (media, FALSE);
- set_target_state (media, state, FALSE);
++ if (klass->start_preroll)
++ if (!klass->start_preroll (media))
++ goto start_failed;
+
+ g_rec_mutex_unlock (&priv->state_lock);
+ preroll_ok = wait_preroll (media);
+ g_rec_mutex_lock (&priv->state_lock);
+
+ if (!preroll_ok)
+ goto preroll_failed;
+ }
+ default:
+ break;
+ }
+
+ return TRUE;
+
+ /* ERRORS */
+ start_failed:
+ {
+ GST_WARNING ("failed to preroll pipeline");
+ return FALSE;
+ }
+ preroll_failed:
+ {
+ GST_WARNING ("failed to preroll pipeline");
+ return FALSE;
+ }
+ }
+
+ static void
+ gst_rtsp_media_unblock_rtcp (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ priv = media->priv;
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ gst_rtsp_stream_unblock_rtcp (stream);
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_unsuspend:
+ * @media: a #GstRTSPMedia
+ *
+ * Unsuspend @media if it was in a suspended state. This method does nothing
+ * when the media was not in the suspended state.
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_unsuspend (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstRTSPMediaClass *klass;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ g_rec_mutex_lock (&priv->state_lock);
+ if (priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
+ goto done;
+
+ klass = GST_RTSP_MEDIA_GET_CLASS (media);
+ if (klass->unsuspend) {
+ if (!klass->unsuspend (media))
+ goto unsuspend_failed;
+ }
+
+ done:
+ gst_rtsp_media_unblock_rtcp (media);
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ unsuspend_failed:
+ {
+ g_rec_mutex_unlock (&priv->state_lock);
+ GST_WARNING ("failed to unsuspend media %p", media);
+ gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_ERROR);
+ return FALSE;
+ }
+ }
+
+ /* must be called with state-lock */
+ static void
+ media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ GstStateChangeReturn set_state_ret;
+ priv->expected_async_done = FALSE;
+
+ if (state == GST_STATE_NULL) {
+ gst_rtsp_media_unprepare (media);
+ } else {
+ GST_INFO ("state %s media %p", gst_element_state_get_name (state), media);
++ gst_rtsp_media_set_target_state (media, state, FALSE);
+
+ if (state == GST_STATE_PLAYING) {
+ /* make sure pads are not blocking anymore when going to PLAYING */
+ media_streams_set_blocked (media, FALSE);
+ }
+
+ /* when we are buffering, don't update the state yet, this will be done
+ * when buffering finishes */
+ if (priv->buffering) {
+ GST_INFO ("Buffering busy, delay state change");
+ } else {
+ if (state == GST_STATE_PAUSED) {
+ set_state_ret = set_state (media, state);
+ if (set_state_ret == GST_STATE_CHANGE_ASYNC)
+ priv->expected_async_done = TRUE;
+ /* and suspend after pause */
+ gst_rtsp_media_suspend (media);
+ } else {
+ set_state (media, state);
+ }
+ }
+ }
+ }
+
+ /**
+ * gst_rtsp_media_set_pipeline_state:
+ * @media: a #GstRTSPMedia
+ * @state: the target state of the pipeline
+ *
+ * Set the state of the pipeline managed by @media to @state
+ */
+ void
+ gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media, GstState state)
+ {
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ g_rec_mutex_lock (&media->priv->state_lock);
+ media_set_pipeline_state_locked (media, state);
+ g_rec_mutex_unlock (&media->priv->state_lock);
+ }
+
+ /**
+ * gst_rtsp_media_set_state:
+ * @media: a #GstRTSPMedia
+ * @state: the target state of the media
+ * @transports: (transfer none) (element-type GstRtspServer.RTSPStreamTransport):
+ * a #GPtrArray of #GstRTSPStreamTransport pointers
+ *
+ * Set the state of @media to @state and for the transports in @transports.
+ *
+ * @media must be prepared with gst_rtsp_media_prepare();
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_media_set_state (GstRTSPMedia * media, GstState state,
+ GPtrArray * transports)
+ {
+ GstRTSPMediaPrivate *priv;
+ gint i;
+ gboolean activate, deactivate, do_state;
+ gint old_active;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ g_return_val_if_fail (transports != NULL, FALSE);
+
+ priv = media->priv;
+
+ g_rec_mutex_lock (&priv->state_lock);
+
+ if (priv->status == GST_RTSP_MEDIA_STATUS_PREPARING
+ && gst_rtsp_media_is_shared (media)) {
+ g_rec_mutex_unlock (&priv->state_lock);
+ gst_rtsp_media_get_status (media);
+ g_rec_mutex_lock (&priv->state_lock);
+ }
+ if (priv->status == GST_RTSP_MEDIA_STATUS_ERROR)
+ goto error_status;
+ if (priv->status != GST_RTSP_MEDIA_STATUS_PREPARED &&
+ priv->status != GST_RTSP_MEDIA_STATUS_SUSPENDED)
+ goto not_prepared;
+
+ /* NULL and READY are the same */
+ if (state == GST_STATE_READY)
+ state = GST_STATE_NULL;
+
+ activate = deactivate = FALSE;
+
+ GST_INFO ("going to state %s media %p, target state %s",
+ gst_element_state_get_name (state), media,
+ gst_element_state_get_name (priv->target_state));
+
+ switch (state) {
+ case GST_STATE_NULL:
+ /* we're going from PLAYING or PAUSED to READY or NULL, deactivate */
+ if (priv->target_state >= GST_STATE_PAUSED)
+ deactivate = TRUE;
+ break;
+ case GST_STATE_PAUSED:
+ /* we're going from PLAYING to PAUSED, deactivate */
+ if (priv->target_state == GST_STATE_PLAYING)
+ deactivate = TRUE;
+ break;
+ case GST_STATE_PLAYING:
+ /* we're going to PLAYING, activate */
+ activate = TRUE;
+ break;
+ default:
+ break;
+ }
+ old_active = priv->n_active;
+
+ GST_DEBUG ("%d transports, activate %d, deactivate %d", transports->len,
+ activate, deactivate);
+ for (i = 0; i < transports->len; i++) {
+ GstRTSPStreamTransport *trans;
+
+ /* we need a non-NULL entry in the array */
+ trans = g_ptr_array_index (transports, i);
+ if (trans == NULL)
+ continue;
+
+ if (activate) {
+ if (gst_rtsp_stream_transport_set_active (trans, TRUE))
+ priv->n_active++;
+ } else if (deactivate) {
+ if (gst_rtsp_stream_transport_set_active (trans, FALSE))
+ priv->n_active--;
+ }
+ }
+
+ if (activate)
+ media_streams_set_blocked (media, FALSE);
+
+ /* we just activated the first media, do the playing state change */
+ if (old_active == 0 && activate)
+ do_state = TRUE;
+ /* if we have no more active media and prepare count is not indicate
+ * that there are new session/sessions ongoing,
+ * do the downward state changes */
+ else if (priv->n_active == 0 && priv->prepare_count <= 1)
+ do_state = TRUE;
+ else
+ do_state = FALSE;
+
+ GST_INFO ("state %d active %d media %p do_state %d", state, priv->n_active,
+ media, do_state);
+
+ if (priv->target_state != state) {
+ if (do_state) {
+ media_set_pipeline_state_locked (media, state);
+ g_signal_emit (media, gst_rtsp_media_signals[SIGNAL_NEW_STATE], 0, state,
+ NULL);
+ }
+ }
+
+ /* remember where we are */
+ if (state != GST_STATE_NULL && (state == GST_STATE_PAUSED ||
+ old_active != priv->n_active)) {
+ g_mutex_lock (&priv->lock);
+ collect_media_stats (media);
+ g_mutex_unlock (&priv->lock);
+ }
+ g_rec_mutex_unlock (&priv->state_lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ not_prepared:
+ {
+ GST_WARNING ("media %p was not prepared", media);
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
+ error_status:
+ {
+ GST_WARNING ("media %p in error status while changing to state %d",
+ media, state);
+ if (state == GST_STATE_NULL) {
+ for (i = 0; i < transports->len; i++) {
+ GstRTSPStreamTransport *trans;
+
+ /* we need a non-NULL entry in the array */
+ trans = g_ptr_array_index (transports, i);
+ if (trans == NULL)
+ continue;
+
+ gst_rtsp_stream_transport_set_active (trans, FALSE);
+ }
+ priv->n_active = 0;
+ }
+ g_rec_mutex_unlock (&priv->state_lock);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_media_set_transport_mode:
+ * @media: a #GstRTSPMedia
+ * @mode: the new value
+ *
+ * Sets if the media pipeline can work in PLAY or RECORD mode
+ */
+ void
+ gst_rtsp_media_set_transport_mode (GstRTSPMedia * media,
+ GstRTSPTransportMode mode)
+ {
+ GstRTSPMediaPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->transport_mode = mode;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_transport_mode:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media can be used for PLAY or RECORD methods.
+ *
+ * Returns: The transport mode.
+ */
+ GstRTSPTransportMode
+ gst_rtsp_media_get_transport_mode (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstRTSPTransportMode res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->transport_mode;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_seekable:
+ * @media: a #GstRTSPMedia
+ *
+ * Check if the pipeline for @media seek and up to what point in time,
+ * it can seek.
+ *
+ * Returns: -1 if the stream is not seekable, 0 if seekable only to the beginning
+ * and > 0 to indicate the longest duration between any two random access points.
+ * %G_MAXINT64 means any value is possible.
+ *
+ * Since: 1.14
+ */
+ GstClockTimeDiff
+ gst_rtsp_media_seekable (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ GstClockTimeDiff res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ /* Currently we are not able to seek on live streams,
+ * and no stream is seekable only to the beginning */
+ g_mutex_lock (&priv->lock);
+ res = priv->seekable;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_media_complete_pipeline:
+ * @media: a #GstRTSPMedia
+ * @transports: (element-type GstRTSPTransport): a list of #GstRTSPTransport
+ *
+ * Add a receiver and sender parts to the pipeline based on the transport from
+ * SETUP.
+ *
+ * Returns: %TRUE if the media pipeline has been sucessfully updated.
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+ g_return_val_if_fail (transports, FALSE);
+
+ GST_DEBUG_OBJECT (media, "complete pipeline");
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStreamTransport *transport;
+ GstRTSPStream *stream;
+ const GstRTSPTransport *rtsp_transport;
+
+ transport = g_ptr_array_index (transports, i);
+ if (!transport)
+ continue;
+
+ stream = gst_rtsp_stream_transport_get_stream (transport);
+ if (!stream)
+ continue;
+
+ rtsp_transport = gst_rtsp_stream_transport_get_transport (transport);
+
+ if (!gst_rtsp_stream_complete_stream (stream, rtsp_transport)) {
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+
+ if (!gst_rtsp_stream_add_transport (stream, transport)) {
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+
+ update_stream_storage_size (media, stream, i);
+ }
+
+ priv->complete = TRUE;
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_media_is_receive_only:
+ *
+ * Returns: %TRUE if @media is receive-only, %FALSE otherwise.
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_media_is_receive_only (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gboolean receive_only;
+
+ g_mutex_lock (&priv->lock);
+ receive_only = is_receive_only (media);
+ g_mutex_unlock (&priv->lock);
+
+ return receive_only;
+ }
+
+ /**
+ * gst_rtsp_media_has_completed_sender:
+ *
+ * See gst_rtsp_stream_is_complete(), gst_rtsp_stream_is_sender().
+ *
+ * Returns: whether @media has at least one complete sender stream.
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_media_has_completed_sender (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv = media->priv;
+ gboolean sender = FALSE;
+ guint i;
+
+ g_mutex_lock (&priv->lock);
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+ if (gst_rtsp_stream_is_complete (stream))
+ if (gst_rtsp_stream_is_sender (stream) ||
+ !gst_rtsp_stream_is_receiver (stream)) {
+ sender = TRUE;
+ break;
+ }
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return sender;
+ }
+
+ /**
+ * gst_rtsp_media_set_rate_control:
+ *
+ * Define whether @media will follow the Rate-Control=no behaviour as specified
+ * in the ONVIF replay spec.
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled)
+ {
+ GstRTSPMediaPrivate *priv;
+ guint i;
+
+ g_return_if_fail (GST_IS_RTSP_MEDIA (media));
+
+ GST_LOG_OBJECT (media, "%s rate control", enabled ? "Enabling" : "Disabling");
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->do_rate_control = enabled;
+ for (i = 0; i < priv->streams->len; i++) {
+ GstRTSPStream *stream = g_ptr_array_index (priv->streams, i);
+
+ gst_rtsp_stream_set_rate_control (stream, enabled);
+
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_media_get_rate_control:
+ *
+ * Returns: whether @media will follow the Rate-Control=no behaviour as specified
+ * in the ONVIF replay spec.
+ *
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_media_get_rate_control (GstRTSPMedia * media)
+ {
+ GstRTSPMediaPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), FALSE);
+
+ priv = media->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->do_rate_control;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
++
++GstElement *
++gst_rtsp_media_get_pipeline (GstRTSPMedia * media)
++{
++ GstRTSPMediaPrivate *priv;
++
++ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
++
++ priv = media->priv;
++
++ g_mutex_lock (&priv->lock);
++ g_object_ref (priv->pipeline);
++ g_mutex_unlock (&priv->lock);
++
++ return priv->pipeline;
++}
++
++
++GstElement *
++gst_rtsp_media_get_rtpbin (GstRTSPMedia * media)
++{
++ GstRTSPMediaPrivate *priv;
++
++ g_return_val_if_fail (GST_IS_RTSP_MEDIA (media), NULL);
++
++ priv = media->priv;
++
++ g_mutex_lock (&priv->lock);
++ g_object_ref (priv->rtpbin);
++ g_mutex_unlock (&priv->lock);
++
++ return priv->rtpbin;
++}
--- /dev/null
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ *
+ * 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 <gst/gst.h>
+ #include <gst/rtsp/rtsp.h>
+ #include <gst/net/gstnet.h>
+
+ #ifndef __GST_RTSP_MEDIA_H__
+ #define __GST_RTSP_MEDIA_H__
+
+ #include "rtsp-server-prelude.h"
+
+ G_BEGIN_DECLS
+
+ /* types for the media */
+ #define GST_TYPE_RTSP_MEDIA (gst_rtsp_media_get_type ())
+ #define GST_IS_RTSP_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_MEDIA))
+ #define GST_IS_RTSP_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_MEDIA))
+ #define GST_RTSP_MEDIA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMediaClass))
+ #define GST_RTSP_MEDIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_MEDIA, GstRTSPMedia))
+ #define GST_RTSP_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_MEDIA, GstRTSPMediaClass))
+ #define GST_RTSP_MEDIA_CAST(obj) ((GstRTSPMedia*)(obj))
+ #define GST_RTSP_MEDIA_CLASS_CAST(klass) ((GstRTSPMediaClass*)(klass))
+
+ typedef struct _GstRTSPMedia GstRTSPMedia;
+ typedef struct _GstRTSPMediaClass GstRTSPMediaClass;
+ typedef struct _GstRTSPMediaPrivate GstRTSPMediaPrivate;
+
+ /**
+ * GstRTSPMediaStatus:
+ * @GST_RTSP_MEDIA_STATUS_UNPREPARED: media pipeline not prerolled
+ * @GST_RTSP_MEDIA_STATUS_UNPREPARING: media pipeline is busy doing a clean
+ * shutdown.
+ * @GST_RTSP_MEDIA_STATUS_PREPARING: media pipeline is prerolling
+ * @GST_RTSP_MEDIA_STATUS_PREPARED: media pipeline is prerolled
+ * @GST_RTSP_MEDIA_STATUS_SUSPENDED: media is suspended
+ * @GST_RTSP_MEDIA_STATUS_ERROR: media pipeline is in error
+ *
+ * The state of the media pipeline.
+ */
+ typedef enum {
+ GST_RTSP_MEDIA_STATUS_UNPREPARED = 0,
+ GST_RTSP_MEDIA_STATUS_UNPREPARING = 1,
+ GST_RTSP_MEDIA_STATUS_PREPARING = 2,
+ GST_RTSP_MEDIA_STATUS_PREPARED = 3,
+ GST_RTSP_MEDIA_STATUS_SUSPENDED = 4,
+ GST_RTSP_MEDIA_STATUS_ERROR = 5
+ } GstRTSPMediaStatus;
+
+ /**
+ * GstRTSPSuspendMode:
+ * @GST_RTSP_SUSPEND_MODE_NONE: Media is not suspended
+ * @GST_RTSP_SUSPEND_MODE_PAUSE: Media is PAUSED in suspend
+ * @GST_RTSP_SUSPEND_MODE_RESET: The media is set to NULL when suspended
+ *
+ * The suspend mode of the media pipeline. A media pipeline is suspended right
+ * after creating the SDP and when the client performs a PAUSED request.
+ */
+ typedef enum {
+ GST_RTSP_SUSPEND_MODE_NONE = 0,
+ GST_RTSP_SUSPEND_MODE_PAUSE = 1,
+ GST_RTSP_SUSPEND_MODE_RESET = 2
+ } GstRTSPSuspendMode;
+
+ /**
+ * GstRTSPTransportMode:
+ * @GST_RTSP_TRANSPORT_MODE_PLAY: Transport supports PLAY mode
+ * @GST_RTSP_TRANSPORT_MODE_RECORD: Transport supports RECORD mode
+ *
+ * The supported modes of the media.
+ */
+ typedef enum {
+ GST_RTSP_TRANSPORT_MODE_PLAY = 1,
+ GST_RTSP_TRANSPORT_MODE_RECORD = 2,
+ } GstRTSPTransportMode;
+
+ /**
+ * GstRTSPPublishClockMode:
+ * @GST_RTSP_PUBLISH_CLOCK_MODE_NONE: Publish nothing
+ * @GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK: Publish the clock but not the offset
+ * @GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET: Publish the clock and offset
+ *
+ * Whether the clock and possibly RTP/clock offset should be published according to RFC7273.
+ */
+ typedef enum {
+ GST_RTSP_PUBLISH_CLOCK_MODE_NONE,
+ GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK,
+ GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK_AND_OFFSET
+ } GstRTSPPublishClockMode;
+
+ #define GST_TYPE_RTSP_TRANSPORT_MODE (gst_rtsp_transport_mode_get_type())
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_transport_mode_get_type (void);
+
+ #define GST_TYPE_RTSP_SUSPEND_MODE (gst_rtsp_suspend_mode_get_type())
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_suspend_mode_get_type (void);
+
+ #define GST_TYPE_RTSP_PUBLISH_CLOCK_MODE (gst_rtsp_publish_clock_mode_get_type())
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_publish_clock_mode_get_type (void);
+
+ #include "rtsp-stream.h"
+ #include "rtsp-thread-pool.h"
+ #include "rtsp-permissions.h"
+ #include "rtsp-address-pool.h"
+ #include "rtsp-sdp.h"
+
+ /**
+ * GstRTSPMedia:
+ *
+ * A class that contains the GStreamer element along with a list of
+ * #GstRTSPStream objects that can produce data.
+ *
+ * This object is usually created from a #GstRTSPMediaFactory.
+ */
+ struct _GstRTSPMedia {
+ GObject parent;
+
+ /*< private >*/
+ GstRTSPMediaPrivate *priv;
+ gpointer _gst_reserved[GST_PADDING];
+ };
+
+ /**
+ * GstRTSPMediaClass:
+ * @handle_message: handle a message
+ * @prepare: the default implementation adds all elements and sets the
+ * pipeline's state to GST_STATE_PAUSED (or GST_STATE_PLAYING
+ * in case of NO_PREROLL elements).
+ * @unprepare: the default implementation sets the pipeline's state
+ * to GST_STATE_NULL and removes all elements.
+ * @suspend: the default implementation sets the pipeline's state to
+ * GST_STATE_NULL GST_STATE_PAUSED depending on the selected
+ * suspend mode.
+ * @unsuspend: the default implementation reverts the suspend operation.
+ * The pipeline will be prerolled again if it's state was
+ * set to GST_STATE_NULL in suspend.
+ * @convert_range: convert a range to the given unit
+ * @query_position: query the current position in the pipeline
+ * @query_stop: query when playback will stop
+ *
+ * The RTSP media class
+ */
+ struct _GstRTSPMediaClass {
+ GObjectClass parent_class;
+
+ /* vmethods */
+ gboolean (*handle_message) (GstRTSPMedia *media, GstMessage *message);
+ gboolean (*prepare) (GstRTSPMedia *media, GstRTSPThread *thread);
++ gboolean (*start_preroll) (GstRTSPMedia *media);
+ gboolean (*unprepare) (GstRTSPMedia *media);
+ gboolean (*suspend) (GstRTSPMedia *media);
+ gboolean (*unsuspend) (GstRTSPMedia *media);
+ gboolean (*convert_range) (GstRTSPMedia *media, GstRTSPTimeRange *range,
+ GstRTSPRangeUnit unit);
+ gboolean (*query_position) (GstRTSPMedia *media, gint64 *position);
+ gboolean (*query_stop) (GstRTSPMedia *media, gint64 *stop);
+ GstElement * (*create_rtpbin) (GstRTSPMedia *media);
+ gboolean (*setup_rtpbin) (GstRTSPMedia *media, GstElement *rtpbin);
+ gboolean (*setup_sdp) (GstRTSPMedia *media, GstSDPMessage *sdp, GstSDPInfo *info);
++ gboolean (*start_prepare) (GstRTSPMedia *media);
+
+ /* signals */
+ void (*new_stream) (GstRTSPMedia *media, GstRTSPStream * stream);
+ void (*removed_stream) (GstRTSPMedia *media, GstRTSPStream * stream);
+
+ void (*prepared) (GstRTSPMedia *media);
+ void (*unprepared) (GstRTSPMedia *media);
+
+ void (*target_state) (GstRTSPMedia *media, GstState state);
+ void (*new_state) (GstRTSPMedia *media, GstState state);
+
+ gboolean (*handle_sdp) (GstRTSPMedia *media, GstSDPMessage *sdp);
+
++ void (*preparing) (GstRTSPMedia *media, GstRTSPStream * stream, guint idx);
++ void (*unpreparing) (GstRTSPMedia *media, GstRTSPStream * stream, guint idx);
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING_LARGE-1];
+ };
+
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_media_get_type (void);
+
+ /* creating the media */
+
+ GST_RTSP_SERVER_API
+ GstRTSPMedia * gst_rtsp_media_new (GstElement *element);
+
+ GST_RTSP_SERVER_API
+ GstElement * gst_rtsp_media_get_element (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_take_pipeline (GstRTSPMedia *media, GstPipeline *pipeline);
+
+ GST_RTSP_SERVER_API
+ GstRTSPMediaStatus gst_rtsp_media_get_status (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_permissions (GstRTSPMedia *media,
+ GstRTSPPermissions *permissions);
+
+ GST_RTSP_SERVER_API
+ GstRTSPPermissions * gst_rtsp_media_get_permissions (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_shared (GstRTSPMedia *media, gboolean shared);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_shared (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_stop_on_disconnect (GstRTSPMedia *media, gboolean stop_on_disconnect);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_stop_on_disconnect (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_transport_mode (GstRTSPMedia *media, GstRTSPTransportMode mode);
+
+ GST_RTSP_SERVER_API
+ GstRTSPTransportMode gst_rtsp_media_get_transport_mode (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_reusable (GstRTSPMedia *media, gboolean reusable);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_reusable (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_profiles (GstRTSPMedia *media, GstRTSPProfile profiles);
+
+ GST_RTSP_SERVER_API
+ GstRTSPProfile gst_rtsp_media_get_profiles (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_protocols (GstRTSPMedia *media, GstRTSPLowerTrans protocols);
+
+ GST_RTSP_SERVER_API
+ GstRTSPLowerTrans gst_rtsp_media_get_protocols (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_eos_shutdown (GstRTSPMedia *media, gboolean eos_shutdown);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_eos_shutdown (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_address_pool (GstRTSPMedia *media, GstRTSPAddressPool *pool);
+
+ GST_RTSP_SERVER_API
+ GstRTSPAddressPool * gst_rtsp_media_get_address_pool (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_multicast_iface (GstRTSPMedia *media, const gchar *multicast_iface);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_media_get_multicast_iface (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_buffer_size (GstRTSPMedia *media, guint size);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_media_get_buffer_size (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_retransmission_time (GstRTSPMedia *media, GstClockTime time);
+
+ GST_RTSP_SERVER_API
+ GstClockTime gst_rtsp_media_get_retransmission_time (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_do_retransmission (GstRTSPMedia * media,
+ gboolean do_retransmission);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_get_do_retransmission (GstRTSPMedia * media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_latency (GstRTSPMedia *media, guint latency);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_media_get_latency (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_use_time_provider (GstRTSPMedia *media, gboolean time_provider);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_time_provider (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ GstNetTimeProvider * gst_rtsp_media_get_time_provider (GstRTSPMedia *media,
+ const gchar *address, guint16 port);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_clock (GstRTSPMedia *media, GstClock * clock);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_publish_clock_mode (GstRTSPMedia * media, GstRTSPPublishClockMode mode);
+
+ GST_RTSP_SERVER_API
+ GstRTSPPublishClockMode gst_rtsp_media_get_publish_clock_mode (GstRTSPMedia * media);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_set_max_mcast_ttl (GstRTSPMedia *media, guint ttl);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_media_get_max_mcast_ttl (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_bind_mcast_address (GstRTSPMedia *media, gboolean bind_mcast_addr);
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_bind_mcast_address (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_dscp_qos (GstRTSPMedia * media, gint dscp_qos);
+ GST_RTSP_SERVER_API
+ gint gst_rtsp_media_get_dscp_qos (GstRTSPMedia * media);
+
+ /* prepare the media for playback */
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_prepare (GstRTSPMedia *media, GstRTSPThread *thread);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_unprepare (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_suspend_mode (GstRTSPMedia *media, GstRTSPSuspendMode mode);
+
+ GST_RTSP_SERVER_API
+ GstRTSPSuspendMode gst_rtsp_media_get_suspend_mode (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_suspend (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_unsuspend (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_setup_sdp (GstRTSPMedia * media, GstSDPMessage * sdp,
+ GstSDPInfo * info);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_handle_sdp (GstRTSPMedia * media, GstSDPMessage * sdp);
+
+ /* creating streams */
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_collect_streams (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ GstRTSPStream * gst_rtsp_media_create_stream (GstRTSPMedia *media,
+ GstElement *payloader,
+ GstPad *pad);
+
+ /* dealing with the media */
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_lock (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_unlock (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ GstClock * gst_rtsp_media_get_clock (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ GstClockTime gst_rtsp_media_get_base_time (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_media_n_streams (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ GstRTSPStream * gst_rtsp_media_get_stream (GstRTSPMedia *media, guint idx);
+
+ GST_RTSP_SERVER_API
+ GstRTSPStream * gst_rtsp_media_find_stream (GstRTSPMedia *media, const gchar * control);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_seek (GstRTSPMedia *media, GstRTSPTimeRange *range);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_seek_full (GstRTSPMedia *media,
+ GstRTSPTimeRange *range,
+ GstSeekFlags flags);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_seek_trickmode (GstRTSPMedia *media,
+ GstRTSPTimeRange *range,
+ GstSeekFlags flags,
+ gdouble rate,
+ GstClockTime trickmode_interval);
+
+ GST_RTSP_SERVER_API
+ GstClockTimeDiff gst_rtsp_media_seekable (GstRTSPMedia *media);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_media_get_range_string (GstRTSPMedia *media,
+ gboolean play,
+ GstRTSPRangeUnit unit);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_get_rates (GstRTSPMedia * media,
+ gdouble * rate,
+ gdouble * applied_rate);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_set_state (GstRTSPMedia *media, GstState state,
+ GPtrArray *transports);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_pipeline_state (GstRTSPMedia * media,
+ GstState state);
+
++GST_RTSP_SERVER_API
++GstStateChangeReturn gst_rtsp_media_set_target_state (GstRTSPMedia * media, GstState state, gboolean do_state);
++
++GST_RTSP_SERVER_API
++void gst_rtsp_media_set_status (GstRTSPMedia * media, GstRTSPMediaStatus status);
++
++GST_RTSP_SERVER_API
++GstElement * gst_rtsp_media_get_pipeline (GstRTSPMedia * media);
++
++GST_RTSP_SERVER_API
++GstElement * gst_rtsp_media_get_rtpbin (GstRTSPMedia * media);
++
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_complete_pipeline (GstRTSPMedia * media, GPtrArray * transports);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_is_receive_only (GstRTSPMedia * media);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_has_completed_sender (GstRTSPMedia * media);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_media_set_rate_control (GstRTSPMedia * media, gboolean enabled);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_media_get_rate_control (GstRTSPMedia * media);
+
+ #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPMedia, gst_object_unref)
+ #endif
+
+ G_END_DECLS
+
+ #endif /* __GST_RTSP_MEDIA_H__ */
--- /dev/null
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ *
+ * 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_OBJECT_H__
+ #define __GST_RTSP_SERVER_OBJECT_H__
+
+ #include <gst/gst.h>
+
+ G_BEGIN_DECLS
+
+ typedef struct _GstRTSPServer GstRTSPServer;
+ typedef struct _GstRTSPServerClass GstRTSPServerClass;
+ typedef struct _GstRTSPServerPrivate GstRTSPServerPrivate;
+
+ #include "rtsp-server-prelude.h"
+ #include "rtsp-session-pool.h"
+ #include "rtsp-session.h"
+ #include "rtsp-media.h"
+ #include "rtsp-stream.h"
+ #include "rtsp-stream-transport.h"
+ #include "rtsp-address-pool.h"
+ #include "rtsp-thread-pool.h"
+ #include "rtsp-client.h"
+ #include "rtsp-context.h"
+ #include "rtsp-mount-points.h"
+ #include "rtsp-media-factory.h"
+ #include "rtsp-permissions.h"
+ #include "rtsp-auth.h"
+ #include "rtsp-token.h"
+ #include "rtsp-session-media.h"
+ #include "rtsp-sdp.h"
+ #include "rtsp-media-factory-uri.h"
+ #include "rtsp-params.h"
+
+ #define GST_TYPE_RTSP_SERVER (gst_rtsp_server_get_type ())
+ #define GST_IS_RTSP_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_SERVER))
+ #define GST_IS_RTSP_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_SERVER))
+ #define GST_RTSP_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_SERVER, GstRTSPServerClass))
+ #define GST_RTSP_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_SERVER, GstRTSPServer))
+ #define GST_RTSP_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_SERVER, GstRTSPServerClass))
+ #define GST_RTSP_SERVER_CAST(obj) ((GstRTSPServer*)(obj))
+ #define GST_RTSP_SERVER_CLASS_CAST(klass) ((GstRTSPServerClass*)(klass))
+
+ /**
+ * GstRTSPServer:
+ *
+ * This object listens on a port, creates and manages the clients connected to
+ * it.
+ */
+ struct _GstRTSPServer {
+ GObject parent;
+
+ /*< private >*/
+ GstRTSPServerPrivate *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: emitted when a new client connected.
+ *
+ * The RTSP server class structure
+ */
+ struct _GstRTSPServerClass {
+ GObjectClass parent_class;
+
+ GstRTSPClient * (*create_client) (GstRTSPServer *server);
++ GSocket * (*create_socket) (GstRTSPServer * server, GCancellable * cancellable, GError ** error);
+
+ /* signals */
+ void (*client_connected) (GstRTSPServer *server, GstRTSPClient *client);
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING_LARGE];
+ };
+
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_server_get_type (void);
+
+ GST_RTSP_SERVER_API
+ GstRTSPServer * gst_rtsp_server_new (void);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_address (GstRTSPServer *server, const gchar *address);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_server_get_address (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_service (GstRTSPServer *server, const gchar *service);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_server_get_service (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_backlog (GstRTSPServer *server, gint backlog);
+
+ GST_RTSP_SERVER_API
+ gint gst_rtsp_server_get_backlog (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ int gst_rtsp_server_get_bound_port (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_session_pool (GstRTSPServer *server, GstRTSPSessionPool *pool);
+
+ GST_RTSP_SERVER_API
+ GstRTSPSessionPool * gst_rtsp_server_get_session_pool (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_mount_points (GstRTSPServer *server, GstRTSPMountPoints *mounts);
+
+ GST_RTSP_SERVER_API
+ GstRTSPMountPoints * gst_rtsp_server_get_mount_points (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_content_length_limit (GstRTSPServer * server, guint limit);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_server_get_content_length_limit (GstRTSPServer * server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_auth (GstRTSPServer *server, GstRTSPAuth *auth);
+
+ GST_RTSP_SERVER_API
+ GstRTSPAuth * gst_rtsp_server_get_auth (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_server_set_thread_pool (GstRTSPServer *server, GstRTSPThreadPool *pool);
+
+ GST_RTSP_SERVER_API
+ GstRTSPThreadPool * gst_rtsp_server_get_thread_pool (GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_server_transfer_connection (GstRTSPServer * server, GSocket *socket,
+ const gchar * ip, gint port,
+ const gchar *initial_buffer);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_server_io_func (GSocket *socket, GIOCondition condition,
+ GstRTSPServer *server);
+
+ GST_RTSP_SERVER_API
+ GSocket * gst_rtsp_server_create_socket (GstRTSPServer *server,
+ GCancellable *cancellable,
+ GError **error);
+
+ GST_RTSP_SERVER_API
+ GSource * gst_rtsp_server_create_source (GstRTSPServer *server,
+ GCancellable * cancellable,
+ GError **error);
+
+ GST_RTSP_SERVER_API
+ 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);
+
+ GST_RTSP_SERVER_API
+ GList * gst_rtsp_server_client_filter (GstRTSPServer *server,
+ GstRTSPServerClientFilterFunc func,
+ gpointer user_data);
+
+ #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPServer, gst_object_unref)
+ #endif
+
+ G_END_DECLS
+
+ #endif /* __GST_RTSP_SERVER_OBJECT_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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)
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <stdlib.h>
++#include <string.h>
++
++#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;
++ guint8 video_codec;
++ gint wfd2_supported;
++ gboolean coupling_mode;
++};
++
++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;
++ server->priv->coupling_mode = FALSE;
++ 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)
++{
++ gchar *server_addr = NULL;
++ GST_INFO_OBJECT (server, "Client is connected");
++
++ server_addr = gst_rtsp_server_get_address (server);
++ gst_rtsp_wfd_client_set_host_address (GST_RTSP_WFD_CLIENT_CAST (client),
++ server_addr);
++ g_free (server_addr);
++ 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_client_set_video_codec (client, priv->video_codec);
++
++ gst_rtsp_wfd_client_set_coupling_mode (client, priv->coupling_mode);
++
++ /* enable or disable R2 features following ini */
++ gst_rtsp_wfd_client_set_wfd2_supported (client, priv->wfd2_supported);
++
++ 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_video_codec (GstRTSPWFDServer * server,
++ guint8 video_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->video_codec = video_codec;
++
++ 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;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_server_set_direct_streaming (GstRTSPWFDServer *server,
++ gint direct_streaming, gchar *urisrc)
++{
++ 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 (GST_RTSP_SERVER(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_set_direct_streaming (GST_RTSP_WFD_CLIENT (client),
++ direct_streaming, urisrc);
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (server, "Failed to set direct streaming to %d", direct_streaming);
++ }
++ g_object_unref (client);
++ }
++
++ return res;
++}
++
++
++GstRTSPResult
++gst_rtsp_wfd_server_set_coupling_mode (GstRTSPWFDServer * server,
++ gboolean coupling_mode)
++{
++ 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->coupling_mode = coupling_mode;
++ GST_RTSP_WFD_SERVER_UNLOCK (server);
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_server_switch_to_udp (GstRTSPWFDServer *server)
++{
++ 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 (GST_RTSP_SERVER(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_switch_to_udp (GST_RTSP_WFD_CLIENT (client));
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (server, "Failed to switch transport to UDP");
++ }
++ g_object_unref (client);
++ }
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_server_switch_to_tcp (GstRTSPWFDServer *server)
++{
++ 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 (GST_RTSP_SERVER(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_switch_to_tcp (GST_RTSP_WFD_CLIENT (client));
++ if (res != GST_RTSP_OK) {
++ GST_ERROR_OBJECT (server, "Failed to switch transport to TCP");
++ }
++ g_object_unref (client);
++ }
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_wfd_server_set_wfd2_supported (GstRTSPWFDServer *server,
++ guint flag)
++{
++ 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->wfd2_supported = flag;
++
++ GST_RTSP_WFD_SERVER_UNLOCK (server);
++ return res;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 2015 Samsung Electronics Hyunjun Ko <zzoon.ko@samsung.com>
++ *
++ * 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 <gst/gst.h>
++
++#include "rtsp-server-prelude.h"
++#include "rtsp-session-pool.h"
++#include "rtsp-mount-points.h"
++#include "rtsp-server.h"
++#include "rtsp-client-wfd.h"
++#include "rtsp-auth.h"
++
++G_BEGIN_DECLS
++
++typedef struct _GstRTSPWFDServer GstRTSPWFDServer;
++typedef struct _GstRTSPWFDServerClass GstRTSPWFDServerClass;
++typedef struct _GstRTSPWFDServerPrivate GstRTSPWFDServerPrivate;
++
++#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];
++};
++
++GST_RTSP_SERVER_API
++GType gst_rtsp_wfd_server_get_type (void);
++
++GST_RTSP_SERVER_API
++GstRTSPWFDServer * gst_rtsp_wfd_server_new (void);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_trigger_request (GstRTSPServer *server, GstWFDTriggerType type);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_supported_reso (GstRTSPWFDServer *server, guint64 supported_reso);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_video_native_reso (GstRTSPWFDServer *server, guint64 native_reso);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_video_codec (GstRTSPWFDServer *server, guint8 video_codec);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_audio_codec (GstRTSPWFDServer *server, guint8 audio_codec);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_direct_streaming (GstRTSPWFDServer *server, gint direct_streaming, gchar *urisrc);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_coupling_mode (GstRTSPWFDServer *server, gboolean coupling_mode);
++
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_switch_to_udp (GstRTSPWFDServer *server);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_switch_to_tcp (GstRTSPWFDServer *server);
++
++GST_RTSP_SERVER_API
++GstRTSPResult gst_rtsp_wfd_server_set_wfd2_supported (GstRTSPWFDServer *server, guint flag);
++
++
++#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__ */
--- /dev/null
- GSocket *socket, *old;
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ *
+ * 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)
+ */
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+
+ #include <stdlib.h>
+ #include <string.h>
+
+ #include "rtsp-context.h"
+ #include "rtsp-server-object.h"
+ #include "rtsp-client.h"
+
+ #define GST_RTSP_SERVER_GET_LOCK(server) (&(GST_RTSP_SERVER_CAST(server)->priv->lock))
+ #define GST_RTSP_SERVER_LOCK(server) (g_mutex_lock(GST_RTSP_SERVER_GET_LOCK(server)))
+ #define GST_RTSP_SERVER_UNLOCK(server) (g_mutex_unlock(GST_RTSP_SERVER_GET_LOCK(server)))
+
+ struct _GstRTSPServerPrivate
+ {
+ GMutex lock; /* protects everything in this struct */
+
+ /* server information */
+ gchar *address;
+ gchar *service;
+ gint backlog;
+
+ GSocket *socket;
+
+ /* sessions on this server */
+ GstRTSPSessionPool *session_pool;
+
+ /* mount points for this server */
+ GstRTSPMountPoints *mount_points;
+
+ /* request size limit */
+ guint content_length_limit;
+
+ /* authentication manager */
+ GstRTSPAuth *auth;
+
+ /* resource manager */
+ GstRTSPThreadPool *thread_pool;
+
+ /* the clients that are connected */
+ GList *clients;
+ guint clients_cookie;
+ };
+
+ #define DEFAULT_ADDRESS "0.0.0.0"
+ #define DEFAULT_BOUND_PORT -1
+ /* #define DEFAULT_ADDRESS "::0" */
+ #define DEFAULT_SERVICE "8554"
+ #define DEFAULT_BACKLOG 5
+
+ /* Define to use the SO_LINGER option so that the server sockets can be resused
+ * sooner. Disabled for now because it is not very well implemented by various
+ * OSes and it causes clients to fail to read the TEARDOWN response. */
+ #undef USE_SOLINGER
+
+ enum
+ {
+ PROP_0,
+ PROP_ADDRESS,
+ PROP_SERVICE,
+ PROP_BOUND_PORT,
+ PROP_BACKLOG,
+
+ PROP_SESSION_POOL,
+ PROP_MOUNT_POINTS,
+ PROP_CONTENT_LENGTH_LIMIT,
+ PROP_LAST
+ };
+
+ enum
+ {
+ SIGNAL_CLIENT_CONNECTED,
+ SIGNAL_LAST
+ };
+
+ G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPServer, gst_rtsp_server, G_TYPE_OBJECT);
+
+ GST_DEBUG_CATEGORY_STATIC (rtsp_server_debug);
+ #define GST_CAT_DEFAULT rtsp_server_debug
+
+ typedef struct _ClientContext ClientContext;
+
+ static guint gst_rtsp_server_signals[SIGNAL_LAST] = { 0 };
+
+ static void gst_rtsp_server_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_server_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_server_finalize (GObject * object);
+
+ static GstRTSPClient *default_create_client (GstRTSPServer * server);
+
+ static void
+ gst_rtsp_server_class_init (GstRTSPServerClass * klass)
+ {
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gst_rtsp_server_get_property;
+ gobject_class->set_property = gst_rtsp_server_set_property;
+ gobject_class->finalize = gst_rtsp_server_finalize;
+
+ /**
+ * GstRTSPServer::address:
+ *
+ * The address of the server. This is the address where the server will
+ * listen on.
+ */
+ g_object_class_install_property (gobject_class, PROP_ADDRESS,
+ g_param_spec_string ("address", "Address",
+ "The address the server uses to listen on", DEFAULT_ADDRESS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPServer::service:
+ *
+ * The service of the server. This is either a string with the service name or
+ * a port number (as a string) the server will listen on.
+ */
+ g_object_class_install_property (gobject_class, PROP_SERVICE,
+ g_param_spec_string ("service", "Service",
+ "The service or port number the server uses to listen on",
+ DEFAULT_SERVICE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPServer::bound-port:
+ *
+ * The actual port the server is listening on. Can be used to retrieve the
+ * port number when the server is started on port 0, which means bind to a
+ * random port. Set to -1 if the server has not been bound yet.
+ */
+ g_object_class_install_property (gobject_class, PROP_BOUND_PORT,
+ g_param_spec_int ("bound-port", "Bound port",
+ "The port number the server is listening on",
+ -1, G_MAXUINT16, DEFAULT_BOUND_PORT,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPServer::backlog:
+ *
+ * The backlog argument defines the maximum length to which the queue of
+ * pending connections for the server may grow. If a connection request arrives
+ * when the queue is full, the client may receive an error with an indication of
+ * ECONNREFUSED or, if the underlying protocol supports retransmission, the
+ * request may be ignored so that a later reattempt at connection succeeds.
+ */
+ g_object_class_install_property (gobject_class, PROP_BACKLOG,
+ g_param_spec_int ("backlog", "Backlog",
+ "The maximum length to which the queue "
+ "of pending connections may grow", 0, G_MAXINT, DEFAULT_BACKLOG,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPServer::session-pool:
+ *
+ * The session pool of the server. By default each server has a separate
+ * session pool but sessions can be shared between servers by setting the same
+ * session pool on multiple servers.
+ */
+ g_object_class_install_property (gobject_class, PROP_SESSION_POOL,
+ g_param_spec_object ("session-pool", "Session Pool",
+ "The session pool to use for client session",
+ GST_TYPE_RTSP_SESSION_POOL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPServer::mount-points:
+ *
+ * The mount points to use for this server. By default the server has no
+ * mount points and thus cannot map urls to media streams.
+ */
+ g_object_class_install_property (gobject_class, PROP_MOUNT_POINTS,
+ g_param_spec_object ("mount-points", "Mount Points",
+ "The mount points to use for client session",
+ GST_TYPE_RTSP_MOUNT_POINTS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * RTSPServer::content-length-limit:
+ *
+ * Define an appropriate request size limit and reject requests exceeding the
+ * limit.
+ *
+ * Since: 1.18
+ */
+ g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH_LIMIT,
+ g_param_spec_uint ("content-length-limit", "Limitation of Content-Length",
+ "Limitation of Content-Length",
+ 0, G_MAXUINT, G_MAXUINT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_rtsp_server_signals[SIGNAL_CLIENT_CONNECTED] =
+ g_signal_new ("client-connected", G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPServerClass, client_connected),
+ NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_RTSP_CLIENT);
+
+ klass->create_client = default_create_client;
++ klass->create_socket = gst_rtsp_server_create_socket;
+
+ GST_DEBUG_CATEGORY_INIT (rtsp_server_debug, "rtspserver", 0, "GstRTSPServer");
+ }
+
+ static void
+ gst_rtsp_server_init (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv = gst_rtsp_server_get_instance_private (server);
+
+ server->priv = priv;
+
+ g_mutex_init (&priv->lock);
+ priv->address = g_strdup (DEFAULT_ADDRESS);
+ priv->service = g_strdup (DEFAULT_SERVICE);
+ priv->socket = NULL;
+ priv->backlog = DEFAULT_BACKLOG;
+ priv->session_pool = gst_rtsp_session_pool_new ();
+ priv->mount_points = gst_rtsp_mount_points_new ();
+ priv->content_length_limit = G_MAXUINT;
+ priv->thread_pool = gst_rtsp_thread_pool_new ();
+ }
+
+ static void
+ gst_rtsp_server_finalize (GObject * object)
+ {
+ GstRTSPServer *server = GST_RTSP_SERVER (object);
+ GstRTSPServerPrivate *priv = server->priv;
+
+ GST_DEBUG_OBJECT (server, "finalize server");
+
+ g_free (priv->address);
+ g_free (priv->service);
+
+ if (priv->socket)
+ g_object_unref (priv->socket);
+
+ if (priv->session_pool)
+ g_object_unref (priv->session_pool);
+ if (priv->mount_points)
+ g_object_unref (priv->mount_points);
+ if (priv->thread_pool)
+ g_object_unref (priv->thread_pool);
+
+ if (priv->auth)
+ g_object_unref (priv->auth);
+
+ g_mutex_clear (&priv->lock);
+
+ G_OBJECT_CLASS (gst_rtsp_server_parent_class)->finalize (object);
+ }
+
+ /**
+ * gst_rtsp_server_new:
+ *
+ * Create a new #GstRTSPServer instance.
+ *
+ * Returns: (transfer full): a new #GstRTSPServer
+ */
+ GstRTSPServer *
+ gst_rtsp_server_new (void)
+ {
+ GstRTSPServer *result;
+
+ result = g_object_new (GST_TYPE_RTSP_SERVER, NULL);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_address:
+ * @server: a #GstRTSPServer
+ * @address: the address
+ *
+ * Configure @server to accept connections on the given address.
+ *
+ * This function must be called before the server is bound.
+ */
+ void
+ gst_rtsp_server_set_address (GstRTSPServer * server, const gchar * address)
+ {
+ GstRTSPServerPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+ g_return_if_fail (address != NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ g_free (priv->address);
+ priv->address = g_strdup (address);
+ GST_RTSP_SERVER_UNLOCK (server);
+ }
+
+ /**
+ * gst_rtsp_server_get_address:
+ * @server: a #GstRTSPServer
+ *
+ * Get the address on which the server will accept connections.
+ *
+ * Returns: (transfer full) (nullable): the server address. g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_server_get_address (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ result = g_strdup (priv->address);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_get_bound_port:
+ * @server: a #GstRTSPServer
+ *
+ * Get the port number where the server was bound to.
+ *
+ * Returns: the port number
+ */
+ int
+ gst_rtsp_server_get_bound_port (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ GSocketAddress *address;
+ int result = -1;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), result);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ if (priv->socket == NULL)
+ goto out;
+
+ address = g_socket_get_local_address (priv->socket, NULL);
+ result = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
+ g_object_unref (address);
+
+ out:
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_service:
+ * @server: a #GstRTSPServer
+ * @service: the service
+ *
+ * Configure @server to accept connections on the given service.
+ * @service should be a string containing the service name (see services(5)) or
+ * a string containing a port number between 1 and 65535.
+ *
+ * When @service is set to "0", the server will listen on a random free
+ * port. The actual used port can be retrieved with
+ * gst_rtsp_server_get_bound_port().
+ *
+ * This function must be called before the server is bound.
+ */
+ void
+ gst_rtsp_server_set_service (GstRTSPServer * server, const gchar * service)
+ {
+ GstRTSPServerPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+ g_return_if_fail (service != NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ g_free (priv->service);
+ priv->service = g_strdup (service);
+ GST_RTSP_SERVER_UNLOCK (server);
+ }
+
+ /**
+ * gst_rtsp_server_get_service:
+ * @server: a #GstRTSPServer
+ *
+ * Get the service on which the server will accept connections.
+ *
+ * Returns: (transfer full) (nullable): the service. use g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_server_get_service (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ result = g_strdup (priv->service);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_backlog:
+ * @server: a #GstRTSPServer
+ * @backlog: the backlog
+ *
+ * configure the maximum amount of requests that may be queued for the
+ * server.
+ *
+ * This function must be called before the server is bound.
+ */
+ void
+ gst_rtsp_server_set_backlog (GstRTSPServer * server, gint backlog)
+ {
+ GstRTSPServerPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ priv->backlog = backlog;
+ GST_RTSP_SERVER_UNLOCK (server);
+ }
+
+ /**
+ * gst_rtsp_server_get_backlog:
+ * @server: a #GstRTSPServer
+ *
+ * The maximum amount of queued requests for the server.
+ *
+ * Returns: the server backlog.
+ */
+ gint
+ gst_rtsp_server_get_backlog (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ gint result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), -1);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ result = priv->backlog;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_session_pool:
+ * @server: a #GstRTSPServer
+ * @pool: (transfer none) (nullable): a #GstRTSPSessionPool
+ *
+ * configure @pool to be used as the session pool of @server.
+ */
+ void
+ gst_rtsp_server_set_session_pool (GstRTSPServer * server,
+ GstRTSPSessionPool * pool)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPSessionPool *old;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+
+ priv = server->priv;
+
+ if (pool)
+ g_object_ref (pool);
+
+ GST_RTSP_SERVER_LOCK (server);
+ old = priv->session_pool;
+ priv->session_pool = pool;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_server_get_session_pool:
+ * @server: a #GstRTSPServer
+ *
+ * Get the #GstRTSPSessionPool used as the session pool of @server.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPSessionPool used for sessions. g_object_unref() after
+ * usage.
+ */
+ GstRTSPSessionPool *
+ gst_rtsp_server_get_session_pool (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPSessionPool *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ if ((result = priv->session_pool))
+ g_object_ref (result);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_mount_points:
+ * @server: a #GstRTSPServer
+ * @mounts: (transfer none) (nullable): a #GstRTSPMountPoints
+ *
+ * configure @mounts to be used as the mount points of @server.
+ */
+ void
+ gst_rtsp_server_set_mount_points (GstRTSPServer * server,
+ GstRTSPMountPoints * mounts)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPMountPoints *old;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+
+ priv = server->priv;
+
+ if (mounts)
+ g_object_ref (mounts);
+
+ GST_RTSP_SERVER_LOCK (server);
+ old = priv->mount_points;
+ priv->mount_points = mounts;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+
+ /**
+ * gst_rtsp_server_get_mount_points:
+ * @server: a #GstRTSPServer
+ *
+ * Get the #GstRTSPMountPoints used as the mount points of @server.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPMountPoints of @server. g_object_unref() after
+ * usage.
+ */
+ GstRTSPMountPoints *
+ gst_rtsp_server_get_mount_points (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPMountPoints *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ if ((result = priv->mount_points))
+ g_object_ref (result);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_content_length_limit
+ * @server: a #GstRTSPServer
+ * Configure @server to use the specified Content-Length limit.
+ *
+ * Define an appropriate request size limit and reject requests exceeding the
+ * limit.
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_server_set_content_length_limit (GstRTSPServer * server, guint limit)
+ {
+ GstRTSPServerPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ priv->content_length_limit = limit;
+ GST_RTSP_SERVER_UNLOCK (server);
+ }
+
+ /**
+ * gst_rtsp_server_get_content_length_limit:
+ * @server: a #GstRTSPServer
+ *
+ * Get the Content-Length limit of @server.
+ *
+ * Returns: the Content-Length limit.
+ *
+ * Since: 1.18
+ */
+ guint
+ gst_rtsp_server_get_content_length_limit (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ guint result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), G_MAXUINT);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ result = priv->content_length_limit;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_auth:
+ * @server: a #GstRTSPServer
+ * @auth: (transfer none) (nullable): a #GstRTSPAuth
+ *
+ * configure @auth to be used as the authentication manager of @server.
+ */
+ void
+ gst_rtsp_server_set_auth (GstRTSPServer * server, GstRTSPAuth * auth)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPAuth *old;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+
+ priv = server->priv;
+
+ if (auth)
+ g_object_ref (auth);
+
+ GST_RTSP_SERVER_LOCK (server);
+ old = priv->auth;
+ priv->auth = auth;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+
+ /**
+ * gst_rtsp_server_get_auth:
+ * @server: a #GstRTSPServer
+ *
+ * Get the #GstRTSPAuth used as the authentication manager of @server.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPAuth of @server. g_object_unref() after
+ * usage.
+ */
+ GstRTSPAuth *
+ gst_rtsp_server_get_auth (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPAuth *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ if ((result = priv->auth))
+ g_object_ref (result);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_server_set_thread_pool:
+ * @server: a #GstRTSPServer
+ * @pool: (transfer none) (nullable): a #GstRTSPThreadPool
+ *
+ * configure @pool to be used as the thread pool of @server.
+ */
+ void
+ gst_rtsp_server_set_thread_pool (GstRTSPServer * server,
+ GstRTSPThreadPool * pool)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPThreadPool *old;
+
+ g_return_if_fail (GST_IS_RTSP_SERVER (server));
+
+ priv = server->priv;
+
+ if (pool)
+ g_object_ref (pool);
+
+ GST_RTSP_SERVER_LOCK (server);
+ old = priv->thread_pool;
+ priv->thread_pool = pool;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_server_get_thread_pool:
+ * @server: a #GstRTSPServer
+ *
+ * Get the #GstRTSPThreadPool used as the thread pool of @server.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPThreadPool of @server. g_object_unref() after
+ * usage.
+ */
+ GstRTSPThreadPool *
+ gst_rtsp_server_get_thread_pool (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv;
+ GstRTSPThreadPool *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ if ((result = priv->thread_pool))
+ g_object_ref (result);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return result;
+ }
+
+ static void
+ gst_rtsp_server_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPServer *server = GST_RTSP_SERVER (object);
+
+ switch (propid) {
+ case PROP_ADDRESS:
+ g_value_take_string (value, gst_rtsp_server_get_address (server));
+ break;
+ case PROP_SERVICE:
+ g_value_take_string (value, gst_rtsp_server_get_service (server));
+ break;
+ case PROP_BOUND_PORT:
+ g_value_set_int (value, gst_rtsp_server_get_bound_port (server));
+ break;
+ case PROP_BACKLOG:
+ g_value_set_int (value, gst_rtsp_server_get_backlog (server));
+ break;
+ case PROP_SESSION_POOL:
+ g_value_take_object (value, gst_rtsp_server_get_session_pool (server));
+ break;
+ case PROP_MOUNT_POINTS:
+ g_value_take_object (value, gst_rtsp_server_get_mount_points (server));
+ break;
+ case PROP_CONTENT_LENGTH_LIMIT:
+ g_value_set_uint (value,
+ gst_rtsp_server_get_content_length_limit (server));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ static void
+ gst_rtsp_server_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPServer *server = GST_RTSP_SERVER (object);
+
+ switch (propid) {
+ case PROP_ADDRESS:
+ gst_rtsp_server_set_address (server, g_value_get_string (value));
+ break;
+ case PROP_SERVICE:
+ gst_rtsp_server_set_service (server, g_value_get_string (value));
+ break;
+ case PROP_BACKLOG:
+ gst_rtsp_server_set_backlog (server, g_value_get_int (value));
+ break;
+ case PROP_SESSION_POOL:
+ gst_rtsp_server_set_session_pool (server, g_value_get_object (value));
+ break;
+ case PROP_MOUNT_POINTS:
+ gst_rtsp_server_set_mount_points (server, g_value_get_object (value));
+ break;
+ case PROP_CONTENT_LENGTH_LIMIT:
+ gst_rtsp_server_set_content_length_limit (server,
+ g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ /**
+ * gst_rtsp_server_create_socket:
+ * @server: a #GstRTSPServer
+ * @cancellable: (allow-none): a #GCancellable
+ * @error: (out): a #GError
+ *
+ * Create a #GSocket for @server. The socket will listen on the
+ * configured service.
+ *
+ * Returns: (transfer full): the #GSocket for @server or %NULL when an error
+ * occurred.
+ */
+ GSocket *
+ gst_rtsp_server_create_socket (GstRTSPServer * server,
+ GCancellable * cancellable, GError ** error)
+ {
+ GstRTSPServerPrivate *priv;
+ GSocketConnectable *conn;
+ GSocketAddressEnumerator *enumerator;
+ GSocket *socket = NULL;
+ #ifdef USE_SOLINGER
+ struct linger linger;
+ #endif
+ GError *sock_error = NULL;
+ GError *bind_error = NULL;
+ guint16 port;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ GST_RTSP_SERVER_LOCK (server);
+ GST_DEBUG_OBJECT (server, "getting address info of %s/%s", priv->address,
+ priv->service);
+
+ /* resolve the server IP address */
+ port = atoi (priv->service);
+ if (port != 0 || !strcmp (priv->service, "0"))
+ conn = g_network_address_new (priv->address, port);
+ else
+ conn = g_network_service_new (priv->service, "tcp", priv->address);
+
+ enumerator = g_socket_connectable_enumerate (conn);
+ g_object_unref (conn);
+
+ /* create server socket, we loop through all the addresses until we manage to
+ * create a socket and bind. */
+ while (TRUE) {
+ GSocketAddress *sockaddr;
+
+ sockaddr =
+ g_socket_address_enumerator_next (enumerator, cancellable, error);
+ if (!sockaddr) {
+ if (!*error)
+ GST_DEBUG_OBJECT (server, "no more addresses %s",
+ *error ? (*error)->message : "");
+ else
+ GST_DEBUG_OBJECT (server, "failed to retrieve next address %s",
+ (*error)->message);
+ break;
+ }
+
+ /* only keep the first error */
+ socket = g_socket_new (g_socket_address_get_family (sockaddr),
+ G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP,
+ sock_error ? NULL : &sock_error);
+
+ if (socket == NULL) {
+ GST_DEBUG_OBJECT (server, "failed to make socket (%s), try next",
+ sock_error->message);
+ g_object_unref (sockaddr);
+ continue;
+ }
+
+ if (g_socket_bind (socket, sockaddr, TRUE, bind_error ? NULL : &bind_error)) {
+ /* ask what port the socket has been bound to */
+ if (port == 0 || !strcmp (priv->service, "0")) {
+ GError *addr_error = NULL;
+
+ g_object_unref (sockaddr);
+ sockaddr = g_socket_get_local_address (socket, &addr_error);
+
+ if (addr_error != NULL) {
+ GST_DEBUG_OBJECT (server,
+ "failed to get the local address of a bound socket %s",
+ addr_error->message);
+ g_clear_error (&addr_error);
+ break;
+ }
+ port =
+ g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr));
+
+ if (port != 0) {
+ g_free (priv->service);
+ priv->service = g_strdup_printf ("%d", port);
+ } else {
+ GST_DEBUG_OBJECT (server, "failed to get the port of a bound socket");
+ }
+ }
+ g_object_unref (sockaddr);
+ break;
+ }
+
+ GST_DEBUG_OBJECT (server, "failed to bind socket (%s), try next",
+ bind_error->message);
+ g_object_unref (sockaddr);
+ g_object_unref (socket);
+ socket = NULL;
+ }
+ g_object_unref (enumerator);
+
+ if (socket == NULL)
+ goto no_socket;
+
+ g_clear_error (&sock_error);
+ g_clear_error (&bind_error);
+
+ GST_DEBUG_OBJECT (server, "opened sending server socket");
+
+ /* keep connection alive; avoids SIGPIPE during write */
+ g_socket_set_keepalive (socket, TRUE);
+
+ #if 0
+ #ifdef USE_SOLINGER
+ /* make sure socket is reset 5 seconds after close. This ensure that we can
+ * reuse the socket quickly while still having a chance to send data to the
+ * client. */
+ linger.l_onoff = 1;
+ linger.l_linger = 5;
+ if (setsockopt (sockfd, SOL_SOCKET, SO_LINGER,
+ (void *) &linger, sizeof (linger)) < 0)
+ goto linger_failed;
+ #endif
+ #endif
+
+ /* set the server socket to nonblocking */
+ g_socket_set_blocking (socket, FALSE);
+
+ /* set listen backlog */
+ g_socket_set_listen_backlog (socket, priv->backlog);
+
+ if (!g_socket_listen (socket, error))
+ goto listen_failed;
+
+ GST_DEBUG_OBJECT (server, "listening on server socket %p with queue of %d",
+ socket, priv->backlog);
+
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return socket;
+
+ /* ERRORS */
+ no_socket:
+ {
+ GST_ERROR_OBJECT (server, "failed to create socket");
+ goto close_error;
+ }
+ #if 0
+ #ifdef USE_SOLINGER
+ linger_failed:
+ {
+ GST_ERROR_OBJECT (server, "failed to no linger socket: %s",
+ g_strerror (errno));
+ goto close_error;
+ }
+ #endif
+ #endif
+ listen_failed:
+ {
+ GST_ERROR_OBJECT (server, "failed to listen on socket: %s",
+ (*error)->message);
+ goto close_error;
+ }
+ close_error:
+ {
+ if (socket)
+ g_object_unref (socket);
+
+ if (sock_error) {
+ if (error == NULL)
+ g_propagate_error (error, sock_error);
+ else
+ g_error_free (sock_error);
+ }
+ if (bind_error) {
+ if ((error == NULL) || (*error == NULL))
+ g_propagate_error (error, bind_error);
+ else
+ g_error_free (bind_error);
+ }
+ GST_RTSP_SERVER_UNLOCK (server);
+ return NULL;
+ }
+ }
+
+ struct _ClientContext
+ {
+ GstRTSPServer *server;
+ GstRTSPThread *thread;
+ GstRTSPClient *client;
+ };
+
+ static gboolean
+ free_client_context (ClientContext * ctx)
+ {
+ GST_DEBUG ("free context %p", ctx);
+
+ GST_RTSP_SERVER_LOCK (ctx->server);
+ if (ctx->thread)
+ gst_rtsp_thread_stop (ctx->thread);
+ GST_RTSP_SERVER_UNLOCK (ctx->server);
+
+ g_object_unref (ctx->client);
+ g_object_unref (ctx->server);
+ g_slice_free (ClientContext, ctx);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ static void
+ unmanage_client (GstRTSPClient * client, ClientContext * ctx)
+ {
+ GstRTSPServer *server = ctx->server;
+ GstRTSPServerPrivate *priv = server->priv;
+
+ GST_DEBUG_OBJECT (server, "unmanage client %p", client);
+
+ GST_RTSP_SERVER_LOCK (server);
+ priv->clients = g_list_remove (priv->clients, ctx);
+ priv->clients_cookie++;
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (ctx->thread) {
+ GSource *src;
+
+ src = g_idle_source_new ();
+ g_source_set_callback (src, (GSourceFunc) free_client_context, ctx, NULL);
+ g_source_attach (src, ctx->thread->context);
+ g_source_unref (src);
+ } else {
+ free_client_context (ctx);
+ }
+ }
+
+ /* add the client context to the active list of clients, takes ownership
+ * of client */
+ static void
+ manage_client (GstRTSPServer * server, GstRTSPClient * client)
+ {
+ ClientContext *cctx;
+ GstRTSPServerPrivate *priv = server->priv;
+ GMainContext *mainctx = NULL;
+ GstRTSPContext ctx = { NULL };
+
+ GST_DEBUG_OBJECT (server, "manage client %p", client);
+
+ g_signal_emit (server, gst_rtsp_server_signals[SIGNAL_CLIENT_CONNECTED], 0,
+ client);
+
+ cctx = g_slice_new0 (ClientContext);
+ cctx->server = g_object_ref (server);
+ cctx->client = client;
+
+ GST_RTSP_SERVER_LOCK (server);
+
+ ctx.server = server;
+ ctx.client = client;
+
+ cctx->thread = gst_rtsp_thread_pool_get_thread (priv->thread_pool,
+ GST_RTSP_THREAD_TYPE_CLIENT, &ctx);
+ if (cctx->thread)
+ mainctx = cctx->thread->context;
+ else {
+ GSource *source;
+ /* find the context to add the watch */
+ if ((source = g_main_current_source ()))
+ mainctx = g_source_get_context (source);
+ }
+
+ g_signal_connect (client, "closed", (GCallback) unmanage_client, cctx);
+ priv->clients = g_list_prepend (priv->clients, cctx);
+ priv->clients_cookie++;
+
+ gst_rtsp_client_attach (client, mainctx);
+
+ GST_RTSP_SERVER_UNLOCK (server);
+ }
+
+ static GstRTSPClient *
+ default_create_client (GstRTSPServer * server)
+ {
+ GstRTSPClient *client;
+ GstRTSPServerPrivate *priv = server->priv;
+
+ /* a new client connected, create a session to handle the client. */
+ client = gst_rtsp_client_new ();
+
+ /* set the session pool that this client should use */
+ GST_RTSP_SERVER_LOCK (server);
+ gst_rtsp_client_set_session_pool (client, priv->session_pool);
+ /* set the mount points that this client should use */
+ gst_rtsp_client_set_mount_points (client, priv->mount_points);
+ /* Set content-length limit */
+ gst_rtsp_client_set_content_length_limit (GST_RTSP_CLIENT (client),
+ priv->content_length_limit);
+ /* set authentication manager */
+ gst_rtsp_client_set_auth (client, priv->auth);
+ /* set threadpool */
+ gst_rtsp_client_set_thread_pool (client, priv->thread_pool);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ return client;
+ }
+
+ /**
+ * gst_rtsp_server_transfer_connection:
+ * @server: a #GstRTSPServer
+ * @socket: (transfer full): a network socket
+ * @ip: the IP address of the remote client
+ * @port: the port used by the other end
+ * @initial_buffer: (nullable): any initial data that was already read from the socket
+ *
+ * Take an existing network socket and use it for an RTSP connection. This
+ * is used when transferring a socket from an HTTP server which should be used
+ * as an RTSP over HTTP tunnel. The @initial_buffer contains any remaining data
+ * that the HTTP server read from the socket while parsing the HTTP header.
+ *
+ * Returns: TRUE if all was ok, FALSE if an error occurred.
+ */
+ gboolean
+ gst_rtsp_server_transfer_connection (GstRTSPServer * server, GSocket * socket,
+ const gchar * ip, gint port, const gchar * initial_buffer)
+ {
+ GstRTSPClient *client = NULL;
+ GstRTSPServerClass *klass;
+ GstRTSPConnection *conn;
+ GstRTSPResult res;
+
+ klass = GST_RTSP_SERVER_GET_CLASS (server);
+
+ if (klass->create_client)
+ client = klass->create_client (server);
+ if (client == NULL)
+ goto client_failed;
+
+ GST_RTSP_CHECK (gst_rtsp_connection_create_from_socket (socket, ip, port,
+ initial_buffer, &conn), no_connection);
+ g_object_unref (socket);
+
+ /* set connection on the client now */
+ gst_rtsp_client_set_connection (client, conn);
+
+ /* manage the client connection */
+ manage_client (server, client);
+
+ return TRUE;
+
+ /* ERRORS */
+ client_failed:
+ {
+ GST_ERROR_OBJECT (server, "failed to create a client");
+ g_object_unref (socket);
+ return FALSE;
+ }
+ no_connection:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+ GST_ERROR ("could not create connection from socket %p: %s", socket, str);
+ g_free (str);
+ g_object_unref (socket);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_server_io_func:
+ * @socket: a #GSocket
+ * @condition: the condition on @source
+ * @server: (transfer none): a #GstRTSPServer
+ *
+ * A default #GSocketSourceFunc that creates a new #GstRTSPClient to accept and handle a
+ * new connection on @socket or @server.
+ *
+ * Returns: TRUE if the source could be connected, FALSE if an error occurred.
+ */
+ gboolean
+ gst_rtsp_server_io_func (GSocket * socket, GIOCondition condition,
+ GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv = server->priv;
+ GstRTSPClient *client = NULL;
+ GstRTSPServerClass *klass;
+ GstRTSPResult res;
+ GstRTSPConnection *conn = NULL;
+ GstRTSPContext ctx = { NULL };
+
+ if (condition & G_IO_IN) {
+ /* a new client connected. */
+ GST_RTSP_CHECK (gst_rtsp_connection_accept (socket, &conn, NULL),
+ accept_failed);
+
+ ctx.server = server;
+ ctx.conn = conn;
+ ctx.auth = priv->auth;
+ gst_rtsp_context_push_current (&ctx);
+
+ if (!gst_rtsp_auth_check (GST_RTSP_AUTH_CHECK_CONNECT))
+ goto connection_refused;
+
+ klass = GST_RTSP_SERVER_GET_CLASS (server);
+ /* a new client connected, create a client object to handle the client. */
+ if (klass->create_client)
+ client = klass->create_client (server);
+ if (client == NULL)
+ goto client_failed;
+
+ /* set connection on the client now */
+ gst_rtsp_client_set_connection (client, conn);
+
+ /* manage the client connection */
+ manage_client (server, client);
+ } else {
+ GST_WARNING_OBJECT (server, "received unknown event %08x", condition);
+ goto exit_no_ctx;
+ }
+ exit:
+ gst_rtsp_context_pop_current (&ctx);
+ exit_no_ctx:
+
+ return G_SOURCE_CONTINUE;
+
+ /* ERRORS */
+ accept_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+ GST_ERROR_OBJECT (server, "Could not accept client on socket %p: %s",
+ socket, str);
+ g_free (str);
+ /* We haven't pushed the context yet, so just return */
+ goto exit_no_ctx;
+ }
+ connection_refused:
+ {
+ GST_ERROR_OBJECT (server, "connection refused");
+ gst_rtsp_connection_free (conn);
+ goto exit;
+ }
+ client_failed:
+ {
+ GST_ERROR_OBJECT (server, "failed to create a client");
+ gst_rtsp_connection_free (conn);
+ goto exit;
+ }
+ }
+
+ static void
+ watch_destroyed (GstRTSPServer * server)
+ {
+ GstRTSPServerPrivate *priv = server->priv;
+
+ GST_DEBUG_OBJECT (server, "source destroyed");
+
+ g_object_unref (priv->socket);
+ priv->socket = NULL;
+ g_object_unref (server);
+ }
+
+ /**
+ * gst_rtsp_server_create_source:
+ * @server: a #GstRTSPServer
+ * @cancellable: (allow-none): a #GCancellable or %NULL.
+ * @error: (out): a #GError
+ *
+ * Create a #GSource for @server. The new source will have a default
+ * #GSocketSourceFunc of gst_rtsp_server_io_func().
+ *
+ * @cancellable if not %NULL can be used to cancel the source, which will cause
+ * the source to trigger, reporting the current condition (which is likely 0
+ * unless cancellation happened at the same time as a condition change). You can
+ * check for this in the callback using g_cancellable_is_cancelled().
+ *
+ * This takes a reference on @server until @source is destroyed.
+ *
+ * Returns: (transfer full): the #GSource for @server or %NULL when an error
+ * occurred. Free with g_source_unref ()
+ */
+ GSource *
+ gst_rtsp_server_create_source (GstRTSPServer * server,
+ GCancellable * cancellable, GError ** error)
+ {
+ GstRTSPServerPrivate *priv;
- socket = gst_rtsp_server_create_socket (server, NULL, error);
++ GstRTSPServerClass *klass;
++ GSocket *socket = NULL, *old;
+ GSource *source;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
++ klass = GST_RTSP_SERVER_GET_CLASS (server);
++
++ if (klass->create_socket)
++ socket = klass->create_socket (server, NULL, error);
+
+ if (socket == NULL)
+ goto no_socket;
+
+ GST_RTSP_SERVER_LOCK (server);
+ old = priv->socket;
+ priv->socket = g_object_ref (socket);
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (old)
+ g_object_unref (old);
+
+ /* create a watch for reads (new connections) and possible errors */
+ source = g_socket_create_source (socket, G_IO_IN |
+ G_IO_ERR | G_IO_HUP | G_IO_NVAL, cancellable);
+ g_object_unref (socket);
+
+ /* configure the callback */
+ g_source_set_callback (source,
+ (GSourceFunc) gst_rtsp_server_io_func, g_object_ref (server),
+ (GDestroyNotify) watch_destroyed);
+
+ return source;
+
+ no_socket:
+ {
+ GST_ERROR_OBJECT (server, "failed to create socket");
+ return NULL;
+ }
+ }
+
+ /**
+ * gst_rtsp_server_attach:
+ * @server: a #GstRTSPServer
+ * @context: (allow-none): a #GMainContext
+ *
+ * Attaches @server to @context. When the mainloop for @context is run, the
+ * server will be dispatched. When @context is %NULL, the default context will be
+ * used).
+ *
+ * This function should be called when the server properties and urls are fully
+ * configured and the server is ready to start.
+ *
+ * This takes a reference on @server until the source is destroyed. Note that
+ * if @context is not the default main context as returned by
+ * g_main_context_default() (or %NULL), g_source_remove() cannot be used to
+ * destroy the source. In that case it is recommended to use
+ * gst_rtsp_server_create_source() and attach it to @context manually.
+ *
+ * Returns: the ID (greater than 0) for the source within the GMainContext.
+ */
+ guint
+ gst_rtsp_server_attach (GstRTSPServer * server, GMainContext * context)
+ {
+ guint res;
+ GSource *source;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), 0);
+
+ source = gst_rtsp_server_create_source (server, NULL, &error);
+ if (source == NULL)
+ goto no_source;
+
+ res = g_source_attach (source, context);
+ g_source_unref (source);
+
+ return res;
+
+ /* ERRORS */
+ no_source:
+ {
+ GST_ERROR_OBJECT (server, "failed to create watch: %s", error->message);
+ g_error_free (error);
+ return 0;
+ }
+ }
+
+ /**
+ * gst_rtsp_server_client_filter:
+ * @server: a #GstRTSPServer
+ * @func: (scope call) (allow-none): a callback
+ * @user_data: user data passed to @func
+ *
+ * Call @func for each client managed by @server. The result value of @func
+ * determines what happens to the client. @func will be called with @server
+ * locked so no further actions on @server can be performed from @func.
+ *
+ * If @func returns #GST_RTSP_FILTER_REMOVE, the client will be removed from
+ * @server.
+ *
+ * If @func returns #GST_RTSP_FILTER_KEEP, the client will remain in @server.
+ *
+ * If @func returns #GST_RTSP_FILTER_REF, the client will remain in @server but
+ * will also be added with an additional ref to the result #GList of this
+ * function..
+ *
+ * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each client.
+ *
+ * Returns: (element-type GstRTSPClient) (transfer full): a #GList with all
+ * clients for which @func returned #GST_RTSP_FILTER_REF. After usage, each
+ * element in the #GList should be unreffed before the list is freed.
+ */
+ GList *
+ gst_rtsp_server_client_filter (GstRTSPServer * server,
+ GstRTSPServerClientFilterFunc func, gpointer user_data)
+ {
+ GstRTSPServerPrivate *priv;
+ GList *result, *walk, *next;
+ GHashTable *visited;
+ guint cookie;
+
+ g_return_val_if_fail (GST_IS_RTSP_SERVER (server), NULL);
+
+ priv = server->priv;
+
+ result = NULL;
+ if (func)
+ visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+
+ GST_RTSP_SERVER_LOCK (server);
+ restart:
+ cookie = priv->clients_cookie;
+ for (walk = priv->clients; walk; walk = next) {
+ ClientContext *cctx = walk->data;
+ GstRTSPClient *client = cctx->client;
+ GstRTSPFilterResult res;
+ gboolean changed;
+
+ next = g_list_next (walk);
+
+ if (func) {
+ /* only visit each media once */
+ if (g_hash_table_contains (visited, client))
+ continue;
+
+ g_hash_table_add (visited, g_object_ref (client));
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ res = func (server, client, user_data);
+
+ GST_RTSP_SERVER_LOCK (server);
+ } else
+ res = GST_RTSP_FILTER_REF;
+
+ changed = (cookie != priv->clients_cookie);
+
+ switch (res) {
+ case GST_RTSP_FILTER_REMOVE:
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ gst_rtsp_client_close (client);
+
+ GST_RTSP_SERVER_LOCK (server);
+ changed |= (cookie != priv->clients_cookie);
+ break;
+ case GST_RTSP_FILTER_REF:
+ result = g_list_prepend (result, g_object_ref (client));
+ break;
+ case GST_RTSP_FILTER_KEEP:
+ default:
+ break;
+ }
+ if (changed)
+ goto restart;
+ }
+ GST_RTSP_SERVER_UNLOCK (server);
+
+ if (func)
+ g_hash_table_unref (visited);
+
+ return result;
+ }
--- /dev/null
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ * Copyright (C) 2015 Centricular Ltd
+ * Author: Sebastian Dröge <sebastian@centricular.com>
+ *
+ * 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-stream
+ * @short_description: A media stream
+ * @see_also: #GstRTSPMedia
+ *
+ * The #GstRTSPStream object manages the data transport for one stream. It
+ * is created from a payloader element and a source pad that produce the RTP
+ * packets for the stream.
+ *
+ * With gst_rtsp_stream_join_bin() the streaming elements are added to the bin
+ * and rtpbin. gst_rtsp_stream_leave_bin() removes the elements again.
+ *
+ * The #GstRTSPStream will use the configured addresspool, as set with
+ * gst_rtsp_stream_set_address_pool(), to allocate multicast addresses for the
+ * stream. With gst_rtsp_stream_get_multicast_address() you can get the
+ * configured address.
+ *
+ * With gst_rtsp_stream_get_server_port () you can get the port that the server
+ * will use to receive RTCP. This is the part that the clients will use to send
+ * RTCP to.
+ *
+ * With gst_rtsp_stream_add_transport() destinations can be added where the
+ * stream should be sent to. Use gst_rtsp_stream_remove_transport() to remove
+ * the destination again.
+ *
+ * Each #GstRTSPStreamTransport spawns one queue that will serve as a backlog of a
+ * controllable maximum size when the reflux from the TCP connection's backpressure
+ * starts spilling all over.
+ *
+ * Unlike the backlog in rtspconnection, which we have decided should only contain
+ * at most one RTP and one RTCP data message in order to allow control messages to
+ * go through unobstructed, this backlog only consists of data messages, allowing
+ * us to fill it up without concern.
+ *
+ * When multiple TCP transports exist, for example in the context of a shared media,
+ * we only pop samples from our appsinks when at least one of the transports doesn't
+ * experience back pressure: this allows us to pace our sample popping to the speed
+ * of the fastest client.
+ *
+ * When a sample is popped, it is either sent directly on transports that don't
+ * experience backpressure, or queued on the transport's backlog otherwise. Samples
+ * are then popped from that backlog when the transport reports it has sent the message.
+ *
+ * Once the backlog reaches an overly large duration, the transport is dropped as
+ * the client was deemed too slow.
+ */
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <string.h>
+
+ #include <gio/gio.h>
+
+ #include <gst/app/gstappsrc.h>
+ #include <gst/app/gstappsink.h>
+
+ #include <gst/rtp/gstrtpbuffer.h>
+
+ #include "rtsp-stream.h"
+ #include "rtsp-server-internal.h"
+
+ struct _GstRTSPStreamPrivate
+ {
+ GMutex lock;
+ guint idx;
+ /* Only one pad is ever set */
+ GstPad *srcpad, *sinkpad;
+ GstElement *payloader;
+ guint buffer_size;
+ GstBin *joined_bin;
+
+ /* TRUE if this stream is running on
+ * the client side of an RTSP link (for RECORD) */
+ gboolean client_side;
+ gchar *control;
+
+ /* TRUE if stream is complete. This means that the receiver and the sender
+ * parts are present in the stream. */
+ gboolean is_complete;
+ GstRTSPProfile profiles;
+ GstRTSPLowerTrans allowed_protocols;
+ GstRTSPLowerTrans configured_protocols;
+
+ /* pads on the rtpbin */
+ GstPad *send_rtp_sink;
+ GstPad *recv_rtp_src;
+ GstPad *recv_sink[2];
+ GstPad *send_src[2];
+
+ /* the RTPSession object */
+ GObject *session;
+
+ /* SRTP encoder/decoder */
+ GstElement *srtpenc;
+ GstElement *srtpdec;
+ GHashTable *keys;
+
+ /* for UDP unicast */
+ GstElement *udpsrc_v4[2];
+ GstElement *udpsrc_v6[2];
+ GstElement *udpqueue[2];
+ GstElement *udpsink[2];
+ GSocket *socket_v4[2];
+ GSocket *socket_v6[2];
+
+ /* for UDP multicast */
+ GstElement *mcast_udpsrc_v4[2];
+ GstElement *mcast_udpsrc_v6[2];
+ GstElement *mcast_udpqueue[2];
+ GstElement *mcast_udpsink[2];
+ GSocket *mcast_socket_v4[2];
+ GSocket *mcast_socket_v6[2];
+ GList *mcast_clients;
+
+ /* for TCP transport */
+ GstElement *appsrc[2];
+ GstClockTime appsrc_base_time[2];
+ GstElement *appqueue[2];
+ GstElement *appsink[2];
+
+ GstElement *tee[2];
+ GstElement *funnel[2];
+
+ /* retransmission */
+ GstElement *rtxsend;
+ GstElement *rtxreceive;
+ guint rtx_pt;
+ GstClockTime rtx_time;
+
+ /* rate control */
+ gboolean do_rate_control;
+
+ /* Forward Error Correction with RFC 5109 */
+ GstElement *ulpfec_decoder;
+ GstElement *ulpfec_encoder;
+ guint ulpfec_pt;
+ gboolean ulpfec_enabled;
+ guint ulpfec_percentage;
+
+ /* pool used to manage unicast and multicast addresses */
+ GstRTSPAddressPool *pool;
+
+ /* unicast server addr/port */
+ GstRTSPAddress *server_addr_v4;
+ GstRTSPAddress *server_addr_v6;
+
+ /* multicast addresses */
+ GstRTSPAddress *mcast_addr_v4;
+ GstRTSPAddress *mcast_addr_v6;
+
+ gchar *multicast_iface;
+ guint max_mcast_ttl;
+ gboolean bind_mcast_address;
+
+ /* the caps of the stream */
+ gulong caps_sig;
+ GstCaps *caps;
+
+ /* transports we stream to */
+ guint n_active;
+ GList *transports;
+ guint transports_cookie;
+ GPtrArray *tr_cache;
+ guint tr_cache_cookie;
+ guint n_tcp_transports;
+ gboolean have_buffer[2];
+
+ gint dscp_qos;
+
+ /* Sending logic for TCP */
+ GThread *send_thread;
+ GCond send_cond;
+ GMutex send_lock;
+ /* @send_lock is released when pushing data out, we use
+ * a cookie to decide whether we should wait on @send_cond
+ * before checking the transports' backlogs again
+ */
+ guint send_cookie;
+ /* Used to control shutdown of @send_thread */
+ gboolean continue_sending;
+
+ /* stream blocking */
+ gulong blocked_id[2];
+ gboolean blocking;
+
+ /* current stream postion */
+ GstClockTime position;
+
+ /* pt->caps map for RECORD streams */
+ GHashTable *ptmap;
+
+ GstRTSPPublishClockMode publish_clock_mode;
+ GThreadPool *send_pool;
+
+ /* Used to provide accurate rtpinfo when the stream is blocking */
+ gboolean blocked_buffer;
+ guint32 blocked_seqnum;
+ guint32 blocked_rtptime;
+ GstClockTime blocked_running_time;
+ gint blocked_clock_rate;
+
+ /* Whether we should send and receive RTCP */
+ gboolean enable_rtcp;
+
+ /* blocking early rtcp packets */
+ GstPad *block_early_rtcp_pad;
+ gulong block_early_rtcp_probe;
+ GstPad *block_early_rtcp_pad_ipv6;
+ gulong block_early_rtcp_probe_ipv6;
+ };
+
+ #define DEFAULT_CONTROL NULL
+ #define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP
+ #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | \
+ GST_RTSP_LOWER_TRANS_TCP
+ #define DEFAULT_MAX_MCAST_TTL 255
+ #define DEFAULT_BIND_MCAST_ADDRESS FALSE
+ #define DEFAULT_DO_RATE_CONTROL TRUE
+ #define DEFAULT_ENABLE_RTCP TRUE
+
+ enum
+ {
+ PROP_0,
+ PROP_CONTROL,
+ PROP_PROFILES,
+ PROP_PROTOCOLS,
+ PROP_LAST
+ };
+
+ enum
+ {
+ SIGNAL_NEW_RTP_ENCODER,
+ SIGNAL_NEW_RTCP_ENCODER,
+ SIGNAL_NEW_RTP_RTCP_DECODER,
++ SIGNAL_RTCP_STATS,
+ SIGNAL_LAST
+ };
+
+ GST_DEBUG_CATEGORY_STATIC (rtsp_stream_debug);
+ #define GST_CAT_DEFAULT rtsp_stream_debug
+
+ static GQuark ssrc_stream_map_key;
+
+ static void gst_rtsp_stream_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_stream_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec);
+
+ static void gst_rtsp_stream_finalize (GObject * obj);
+
+ static gboolean
+ update_transport (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
+ gboolean add);
+
+ static guint gst_rtsp_stream_signals[SIGNAL_LAST] = { 0 };
+
+ G_DEFINE_TYPE_WITH_PRIVATE (GstRTSPStream, gst_rtsp_stream, G_TYPE_OBJECT);
+
+ static void
+ gst_rtsp_stream_class_init (GstRTSPStreamClass * klass)
+ {
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gst_rtsp_stream_get_property;
+ gobject_class->set_property = gst_rtsp_stream_set_property;
+ gobject_class->finalize = gst_rtsp_stream_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_CONTROL,
+ g_param_spec_string ("control", "Control",
+ "The control string for this stream", DEFAULT_CONTROL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PROFILES,
+ g_param_spec_flags ("profiles", "Profiles",
+ "Allowed transfer profiles", GST_TYPE_RTSP_PROFILE,
+ DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
+ g_param_spec_flags ("protocols", "Protocols",
+ "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
+ DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_rtsp_stream_signals[SIGNAL_NEW_RTP_ENCODER] =
+ g_signal_new ("new-rtp-encoder", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ gst_rtsp_stream_signals[SIGNAL_NEW_RTCP_ENCODER] =
+ g_signal_new ("new-rtcp-encoder", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ gst_rtsp_stream_signals[SIGNAL_NEW_RTP_RTCP_DECODER] =
+ g_signal_new ("new-rtp-rtcp-decoder", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
++ gst_rtsp_stream_signals[SIGNAL_RTCP_STATS] =
++ g_signal_new ("rtcp-statistics", G_TYPE_FROM_CLASS (klass),
++ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
++ G_TYPE_NONE, 1, GST_TYPE_STRUCTURE);
++
+ GST_DEBUG_CATEGORY_INIT (rtsp_stream_debug, "rtspstream", 0, "GstRTSPStream");
+
+ ssrc_stream_map_key = g_quark_from_static_string ("GstRTSPServer.stream");
+ }
+
+ static void
+ gst_rtsp_stream_init (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = gst_rtsp_stream_get_instance_private (stream);
+
+ GST_DEBUG ("new stream %p", stream);
+
+ stream->priv = priv;
+
+ priv->dscp_qos = -1;
+ priv->control = g_strdup (DEFAULT_CONTROL);
+ priv->profiles = DEFAULT_PROFILES;
+ priv->allowed_protocols = DEFAULT_PROTOCOLS;
+ priv->configured_protocols = 0;
+ priv->publish_clock_mode = GST_RTSP_PUBLISH_CLOCK_MODE_CLOCK;
+ priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL;
+ priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS;
+ priv->do_rate_control = DEFAULT_DO_RATE_CONTROL;
+ priv->enable_rtcp = DEFAULT_ENABLE_RTCP;
+
+ g_mutex_init (&priv->lock);
+
+ priv->continue_sending = TRUE;
+ priv->send_cookie = 0;
+ g_cond_init (&priv->send_cond);
+ g_mutex_init (&priv->send_lock);
+
+ priv->keys = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) gst_caps_unref);
+ priv->ptmap = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) gst_caps_unref);
+ priv->send_pool = NULL;
+ priv->block_early_rtcp_pad = NULL;
+ priv->block_early_rtcp_probe = 0;
+ priv->block_early_rtcp_pad_ipv6 = NULL;
+ priv->block_early_rtcp_probe_ipv6 = 0;
+ }
+
+ typedef struct _UdpClientAddrInfo UdpClientAddrInfo;
+
+ struct _UdpClientAddrInfo
+ {
+ gchar *address;
+ guint rtp_port;
+ guint add_count; /* how often this address has been added */
+ };
+
+ static void
+ free_mcast_client (gpointer data)
+ {
+ UdpClientAddrInfo *client = data;
+
+ g_free (client->address);
+ g_free (client);
+ }
+
+ static void
+ gst_rtsp_stream_finalize (GObject * obj)
+ {
+ GstRTSPStream *stream;
+ GstRTSPStreamPrivate *priv;
+ guint i;
+
+ stream = GST_RTSP_STREAM (obj);
+ priv = stream->priv;
+
+ GST_DEBUG ("finalize stream %p", stream);
+
+ /* we really need to be unjoined now */
+ g_return_if_fail (priv->joined_bin == NULL);
+
+ if (priv->send_pool)
+ g_thread_pool_free (priv->send_pool, TRUE, TRUE);
+ if (priv->mcast_addr_v4)
+ gst_rtsp_address_free (priv->mcast_addr_v4);
+ if (priv->mcast_addr_v6)
+ gst_rtsp_address_free (priv->mcast_addr_v6);
+ if (priv->server_addr_v4)
+ gst_rtsp_address_free (priv->server_addr_v4);
+ if (priv->server_addr_v6)
+ gst_rtsp_address_free (priv->server_addr_v6);
+ if (priv->pool)
+ g_object_unref (priv->pool);
+ if (priv->rtxsend)
+ g_object_unref (priv->rtxsend);
+ if (priv->rtxreceive)
+ g_object_unref (priv->rtxreceive);
+ if (priv->ulpfec_encoder)
+ gst_object_unref (priv->ulpfec_encoder);
+ if (priv->ulpfec_decoder)
+ gst_object_unref (priv->ulpfec_decoder);
+
+ for (i = 0; i < 2; i++) {
+ if (priv->socket_v4[i])
+ g_object_unref (priv->socket_v4[i]);
+ if (priv->socket_v6[i])
+ g_object_unref (priv->socket_v6[i]);
+ if (priv->mcast_socket_v4[i])
+ g_object_unref (priv->mcast_socket_v4[i]);
+ if (priv->mcast_socket_v6[i])
+ g_object_unref (priv->mcast_socket_v6[i]);
+ }
+
+ g_free (priv->multicast_iface);
+ g_list_free_full (priv->mcast_clients, (GDestroyNotify) free_mcast_client);
+
+ gst_object_unref (priv->payloader);
+ if (priv->srcpad)
+ gst_object_unref (priv->srcpad);
+ if (priv->sinkpad)
+ gst_object_unref (priv->sinkpad);
+ g_free (priv->control);
+ g_mutex_clear (&priv->lock);
+
+ g_hash_table_unref (priv->keys);
+ g_hash_table_destroy (priv->ptmap);
+
+ g_mutex_clear (&priv->send_lock);
+ g_cond_clear (&priv->send_cond);
+
+ if (priv->block_early_rtcp_probe != 0) {
+ gst_pad_remove_probe
+ (priv->block_early_rtcp_pad, priv->block_early_rtcp_probe);
+ gst_object_unref (priv->block_early_rtcp_pad);
+ }
+
+ if (priv->block_early_rtcp_probe_ipv6 != 0) {
+ gst_pad_remove_probe
+ (priv->block_early_rtcp_pad_ipv6, priv->block_early_rtcp_probe_ipv6);
+ gst_object_unref (priv->block_early_rtcp_pad_ipv6);
+ }
+
+ G_OBJECT_CLASS (gst_rtsp_stream_parent_class)->finalize (obj);
+ }
+
+ static void
+ gst_rtsp_stream_get_property (GObject * object, guint propid,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPStream *stream = GST_RTSP_STREAM (object);
+
+ switch (propid) {
+ case PROP_CONTROL:
+ g_value_take_string (value, gst_rtsp_stream_get_control (stream));
+ break;
+ case PROP_PROFILES:
+ g_value_set_flags (value, gst_rtsp_stream_get_profiles (stream));
+ break;
+ case PROP_PROTOCOLS:
+ g_value_set_flags (value, gst_rtsp_stream_get_protocols (stream));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ static void
+ gst_rtsp_stream_set_property (GObject * object, guint propid,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPStream *stream = GST_RTSP_STREAM (object);
+
+ switch (propid) {
+ case PROP_CONTROL:
+ gst_rtsp_stream_set_control (stream, g_value_get_string (value));
+ break;
+ case PROP_PROFILES:
+ gst_rtsp_stream_set_profiles (stream, g_value_get_flags (value));
+ break;
+ case PROP_PROTOCOLS:
+ gst_rtsp_stream_set_protocols (stream, g_value_get_flags (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_new:
+ * @idx: an index
+ * @pad: a #GstPad
+ * @payloader: a #GstElement
+ *
+ * Create a new media stream with index @idx that handles RTP data on
+ * @pad and has a payloader element @payloader if @pad is a source pad
+ * or a depayloader element @payloader if @pad is a sink pad.
+ *
+ * Returns: (transfer full): a new #GstRTSPStream
+ */
+ GstRTSPStream *
+ gst_rtsp_stream_new (guint idx, GstElement * payloader, GstPad * pad)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPStream *stream;
+
+ g_return_val_if_fail (GST_IS_ELEMENT (payloader), NULL);
+ g_return_val_if_fail (GST_IS_PAD (pad), NULL);
+
+ stream = g_object_new (GST_TYPE_RTSP_STREAM, NULL);
+ priv = stream->priv;
+ priv->idx = idx;
+ priv->payloader = gst_object_ref (payloader);
+ if (GST_PAD_IS_SRC (pad))
+ priv->srcpad = gst_object_ref (pad);
+ else
+ priv->sinkpad = gst_object_ref (pad);
+
+ return stream;
+ }
+
+ /**
+ * gst_rtsp_stream_get_index:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the stream index.
+ *
+ * Return: the stream index.
+ */
+ guint
+ gst_rtsp_stream_get_index (GstRTSPStream * stream)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), -1);
+
+ return stream->priv->idx;
+ }
+
+ /**
+ * gst_rtsp_stream_get_pt:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the stream payload type.
+ *
+ * Return: the stream payload type.
+ */
+ guint
+ gst_rtsp_stream_get_pt (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ guint pt;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), -1);
+
+ priv = stream->priv;
+
+ g_object_get (G_OBJECT (priv->payloader), "pt", &pt, NULL);
+
+ return pt;
+ }
+
+ /**
+ * gst_rtsp_stream_get_srcpad:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the srcpad associated with @stream.
+ *
+ * Returns: (transfer full) (nullable): the srcpad. Unref after usage.
+ */
+ GstPad *
+ gst_rtsp_stream_get_srcpad (GstRTSPStream * stream)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ if (!stream->priv->srcpad)
+ return NULL;
+
+ return gst_object_ref (stream->priv->srcpad);
+ }
+
+ /**
+ * gst_rtsp_stream_get_sinkpad:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the sinkpad associated with @stream.
+ *
+ * Returns: (transfer full) (nullable): the sinkpad. Unref after usage.
+ */
+ GstPad *
+ gst_rtsp_stream_get_sinkpad (GstRTSPStream * stream)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ if (!stream->priv->sinkpad)
+ return NULL;
+
+ return gst_object_ref (stream->priv->sinkpad);
+ }
+
+ /**
+ * gst_rtsp_stream_get_control:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the control string to identify this stream.
+ *
+ * Returns: (transfer full) (nullable): the control string. g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_stream_get_control (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = g_strdup (priv->control)) == NULL)
+ result = g_strdup_printf ("stream=%u", priv->idx);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_stream_set_control:
+ * @stream: a #GstRTSPStream
+ * @control: (nullable): a control string
+ *
+ * Set the control string in @stream.
+ */
+ void
+ gst_rtsp_stream_set_control (GstRTSPStream * stream, const gchar * control)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ g_free (priv->control);
+ priv->control = g_strdup (control);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_has_control:
+ * @stream: a #GstRTSPStream
+ * @control: (nullable): a control string
+ *
+ * Check if @stream has the control string @control.
+ *
+ * Returns: %TRUE is @stream has @control as the control string
+ */
+ gboolean
+ gst_rtsp_stream_has_control (GstRTSPStream * stream, const gchar * control)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (priv->control)
+ res = (g_strcmp0 (priv->control, control) == 0);
+ else {
+ guint streamid;
+
+ if (sscanf (control, "stream=%u", &streamid) > 0)
+ res = (streamid == priv->idx);
+ else
+ res = FALSE;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_set_mtu:
+ * @stream: a #GstRTSPStream
+ * @mtu: a new MTU
+ *
+ * Configure the mtu in the payloader of @stream to @mtu.
+ */
+ void
+ gst_rtsp_stream_set_mtu (GstRTSPStream * stream, guint mtu)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ GST_LOG_OBJECT (stream, "set MTU %u", mtu);
+
+ g_object_set (G_OBJECT (priv->payloader), "mtu", mtu, NULL);
+ }
+
+ /**
+ * gst_rtsp_stream_get_mtu:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the configured MTU in the payloader of @stream.
+ *
+ * Returns: the MTU of the payloader.
+ */
+ guint
+ gst_rtsp_stream_get_mtu (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ guint mtu;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);
+
+ priv = stream->priv;
+
+ g_object_get (G_OBJECT (priv->payloader), "mtu", &mtu, NULL);
+
+ return mtu;
+ }
+
+ /* Update the dscp qos property on the udp sinks */
+ static void
+ update_dscp_qos (GstRTSPStream * stream, GstElement ** udpsink)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ priv = stream->priv;
+
+ if (*udpsink) {
+ g_object_set (G_OBJECT (*udpsink), "qos-dscp", priv->dscp_qos, NULL);
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_set_dscp_qos:
+ * @stream: a #GstRTSPStream
+ * @dscp_qos: a new dscp qos value (0-63, or -1 to disable)
+ *
+ * Configure the dscp qos of the outgoing sockets to @dscp_qos.
+ */
+ void
+ gst_rtsp_stream_set_dscp_qos (GstRTSPStream * stream, gint dscp_qos)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ GST_LOG_OBJECT (stream, "set DSCP QoS %d", dscp_qos);
+
+ if (dscp_qos < -1 || dscp_qos > 63) {
+ GST_WARNING_OBJECT (stream, "trying to set illegal dscp qos %d", dscp_qos);
+ return;
+ }
+
+ priv->dscp_qos = dscp_qos;
+
+ update_dscp_qos (stream, priv->udpsink);
+ }
+
+ /**
+ * gst_rtsp_stream_get_dscp_qos:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the configured DSCP QoS in of the outgoing sockets.
+ *
+ * Returns: the DSCP QoS value of the outgoing sockets, or -1 if disbled.
+ */
+ gint
+ gst_rtsp_stream_get_dscp_qos (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), -1);
+
+ priv = stream->priv;
+
+ return priv->dscp_qos;
+ }
+
+ /**
+ * gst_rtsp_stream_is_transport_supported:
+ * @stream: a #GstRTSPStream
+ * @transport: (transfer none): a #GstRTSPTransport
+ *
+ * Check if @transport can be handled by stream
+ *
+ * Returns: %TRUE if @transport can be handled by @stream.
+ */
+ gboolean
+ gst_rtsp_stream_is_transport_supported (GstRTSPStream * stream,
+ GstRTSPTransport * transport)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ g_return_val_if_fail (transport != NULL, FALSE);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (transport->trans != GST_RTSP_TRANS_RTP)
+ goto unsupported_transmode;
+
+ if (!(transport->profile & priv->profiles))
+ goto unsupported_profile;
+
+ if (!(transport->lower_transport & priv->allowed_protocols))
+ goto unsupported_ltrans;
+
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ unsupported_transmode:
+ {
+ GST_DEBUG ("unsupported transport mode %d", transport->trans);
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ unsupported_profile:
+ {
+ GST_DEBUG ("unsupported profile %d", transport->profile);
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ unsupported_ltrans:
+ {
+ GST_DEBUG ("unsupported lower transport %d", transport->lower_transport);
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_set_profiles:
+ * @stream: a #GstRTSPStream
+ * @profiles: the new profiles
+ *
+ * Configure the allowed profiles for @stream.
+ */
+ void
+ gst_rtsp_stream_set_profiles (GstRTSPStream * stream, GstRTSPProfile profiles)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->profiles = profiles;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_profiles:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the allowed profiles of @stream.
+ *
+ * Returns: a #GstRTSPProfile
+ */
+ GstRTSPProfile
+ gst_rtsp_stream_get_profiles (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPProfile res;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), GST_RTSP_PROFILE_UNKNOWN);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->profiles;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_set_protocols:
+ * @stream: a #GstRTSPStream
+ * @protocols: the new flags
+ *
+ * Configure the allowed lower transport for @stream.
+ */
+ void
+ gst_rtsp_stream_set_protocols (GstRTSPStream * stream,
+ GstRTSPLowerTrans protocols)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ priv->allowed_protocols = protocols;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_protocols:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the allowed protocols of @stream.
+ *
+ * Returns: a #GstRTSPLowerTrans
+ */
+ GstRTSPLowerTrans
+ gst_rtsp_stream_get_protocols (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPLowerTrans res;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream),
+ GST_RTSP_LOWER_TRANS_UNKNOWN);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ res = priv->allowed_protocols;
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_set_address_pool:
+ * @stream: a #GstRTSPStream
+ * @pool: (transfer none) (nullable): a #GstRTSPAddressPool
+ *
+ * configure @pool to be used as the address pool of @stream.
+ */
+ void
+ gst_rtsp_stream_set_address_pool (GstRTSPStream * stream,
+ GstRTSPAddressPool * pool)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPAddressPool *old;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ GST_LOG_OBJECT (stream, "set address pool %p", pool);
+
+ g_mutex_lock (&priv->lock);
+ if ((old = priv->pool) != pool)
+ priv->pool = pool ? g_object_ref (pool) : NULL;
+ else
+ old = NULL;
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_object_unref (old);
+ }
+
+ /**
+ * gst_rtsp_stream_get_address_pool:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the #GstRTSPAddressPool used as the address pool of @stream.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPAddressPool of @stream.
+ * g_object_unref() after usage.
+ */
+ GstRTSPAddressPool *
+ gst_rtsp_stream_get_address_pool (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPAddressPool *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->pool))
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_stream_set_multicast_iface:
+ * @stream: a #GstRTSPStream
+ * @multicast_iface: (transfer none) (nullable): a multicast interface name
+ *
+ * configure @multicast_iface to be used for @stream.
+ */
+ void
+ gst_rtsp_stream_set_multicast_iface (GstRTSPStream * stream,
+ const gchar * multicast_iface)
+ {
+ GstRTSPStreamPrivate *priv;
+ gchar *old;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ GST_LOG_OBJECT (stream, "set multicast iface %s",
+ GST_STR_NULL (multicast_iface));
+
+ g_mutex_lock (&priv->lock);
+ if ((old = priv->multicast_iface) != multicast_iface)
+ priv->multicast_iface = multicast_iface ? g_strdup (multicast_iface) : NULL;
+ else
+ old = NULL;
+ g_mutex_unlock (&priv->lock);
+
+ if (old)
+ g_free (old);
+ }
+
+ /**
+ * gst_rtsp_stream_get_multicast_iface:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the multicast interface used for @stream.
+ *
+ * Returns: (transfer full) (nullable): the multicast interface for @stream.
+ * g_free() after usage.
+ */
+ gchar *
+ gst_rtsp_stream_get_multicast_iface (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gchar *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->multicast_iface))
+ result = g_strdup (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_stream_get_multicast_address:
+ * @stream: a #GstRTSPStream
+ * @family: the #GSocketFamily
+ *
+ * Get the multicast address of @stream for @family. The original
+ * #GstRTSPAddress is cached and copy is returned, so freeing the return value
+ * won't release the address from the pool.
+ *
+ * Returns: (transfer full) (nullable): the #GstRTSPAddress of @stream
+ * or %NULL when no address could be allocated. gst_rtsp_address_free()
+ * after usage.
+ */
+ GstRTSPAddress *
+ gst_rtsp_stream_get_multicast_address (GstRTSPStream * stream,
+ GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPAddress *result;
+ GstRTSPAddress **addrp;
+ GstRTSPAddressFlags flags;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&stream->priv->lock);
+
+ if (family == G_SOCKET_FAMILY_IPV6) {
+ flags = GST_RTSP_ADDRESS_FLAG_IPV6;
+ addrp = &priv->mcast_addr_v6;
+ } else {
+ flags = GST_RTSP_ADDRESS_FLAG_IPV4;
+ addrp = &priv->mcast_addr_v4;
+ }
+
+ if (*addrp == NULL) {
+ if (priv->pool == NULL)
+ goto no_pool;
+
+ flags |= GST_RTSP_ADDRESS_FLAG_EVEN_PORT | GST_RTSP_ADDRESS_FLAG_MULTICAST;
+
+ *addrp = gst_rtsp_address_pool_acquire_address (priv->pool, flags, 2);
+ if (*addrp == NULL)
+ goto no_address;
+
+ /* FIXME: Also reserve the same port with unicast ANY address, since that's
+ * where we are going to bind our socket. Probably loop until we find a port
+ * available in both mcast and unicast pools. Maybe GstRTSPAddressPool
+ * should do it for us when both GST_RTSP_ADDRESS_FLAG_MULTICAST and
+ * GST_RTSP_ADDRESS_FLAG_UNICAST are givent. */
+ }
+ result = gst_rtsp_address_copy (*addrp);
+
+ g_mutex_unlock (&stream->priv->lock);
+
+ return result;
+
+ /* ERRORS */
+ no_pool:
+ {
+ GST_ERROR_OBJECT (stream, "no address pool specified");
+ g_mutex_unlock (&stream->priv->lock);
+ return NULL;
+ }
+ no_address:
+ {
+ GST_ERROR_OBJECT (stream, "failed to acquire address from pool");
+ g_mutex_unlock (&stream->priv->lock);
+ return NULL;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_reserve_address:
+ * @stream: a #GstRTSPStream
+ * @address: an address
+ * @port: a port
+ * @n_ports: n_ports
+ * @ttl: a TTL
+ *
+ * Reserve @address and @port as the address and port of @stream. The original
+ * #GstRTSPAddress is cached and copy is returned, so freeing the return value
+ * won't release the address from the pool.
+ *
+ * Returns: (nullable): the #GstRTSPAddress of @stream or %NULL when
+ * the address could not be reserved. gst_rtsp_address_free() after
+ * usage.
+ */
+ GstRTSPAddress *
+ gst_rtsp_stream_reserve_address (GstRTSPStream * stream,
+ const gchar * address, guint port, guint n_ports, guint ttl)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPAddress *result;
+ GInetAddress *addr;
+ GSocketFamily family;
+ GstRTSPAddress **addrp;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ g_return_val_if_fail (address != NULL, NULL);
+ g_return_val_if_fail (port > 0, NULL);
+ g_return_val_if_fail (n_ports > 0, NULL);
+ g_return_val_if_fail (ttl > 0, NULL);
+
+ priv = stream->priv;
+
+ addr = g_inet_address_new_from_string (address);
+ if (!addr) {
+ GST_ERROR ("failed to get inet addr from %s", address);
+ family = G_SOCKET_FAMILY_IPV4;
+ } else {
+ family = g_inet_address_get_family (addr);
+ g_object_unref (addr);
+ }
+
+ if (family == G_SOCKET_FAMILY_IPV6)
+ addrp = &priv->mcast_addr_v6;
+ else
+ addrp = &priv->mcast_addr_v4;
+
+ g_mutex_lock (&priv->lock);
+ if (*addrp == NULL) {
+ GstRTSPAddressPoolResult res;
+
+ if (priv->pool == NULL)
+ goto no_pool;
+
+ res = gst_rtsp_address_pool_reserve_address (priv->pool, address,
+ port, n_ports, ttl, addrp);
+ if (res != GST_RTSP_ADDRESS_POOL_OK)
+ goto no_address;
+
+ /* FIXME: Also reserve the same port with unicast ANY address, since that's
+ * where we are going to bind our socket. */
+ } else {
+ if (g_ascii_strcasecmp ((*addrp)->address, address) ||
+ (*addrp)->port != port || (*addrp)->n_ports != n_ports ||
+ (*addrp)->ttl != ttl)
+ goto different_address;
+ }
+ result = gst_rtsp_address_copy (*addrp);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+
+ /* ERRORS */
+ no_pool:
+ {
+ GST_ERROR_OBJECT (stream, "no address pool specified");
+ g_mutex_unlock (&priv->lock);
+ return NULL;
+ }
+ no_address:
+ {
+ GST_ERROR_OBJECT (stream, "failed to acquire address %s from pool",
+ address);
+ g_mutex_unlock (&priv->lock);
+ return NULL;
+ }
+ different_address:
+ {
+ GST_ERROR_OBJECT (stream,
+ "address %s is not the same as %s that was already reserved",
+ address, (*addrp)->address);
+ g_mutex_unlock (&priv->lock);
+ return NULL;
+ }
+ }
+
+ /* must be called with lock */
+ static void
+ set_socket_for_udpsink (GstElement * udpsink, GSocket * socket,
+ GSocketFamily family)
+ {
+ const gchar *multisink_socket;
+
+ if (family == G_SOCKET_FAMILY_IPV6)
+ multisink_socket = "socket-v6";
+ else
+ multisink_socket = "socket";
+
+ g_object_set (G_OBJECT (udpsink), multisink_socket, socket, NULL);
+ }
+
+ /* must be called with lock */
+ static void
+ set_multicast_socket_for_udpsink (GstElement * udpsink, GSocket * socket,
+ GSocketFamily family, const gchar * multicast_iface,
+ const gchar * addr_str, gint port, gint mcast_ttl)
+ {
+ set_socket_for_udpsink (udpsink, socket, family);
+
+ if (multicast_iface) {
+ GST_INFO ("setting multicast-iface %s", multicast_iface);
+ g_object_set (G_OBJECT (udpsink), "multicast-iface", multicast_iface, NULL);
+ }
+
+ if (mcast_ttl > 0) {
+ GST_INFO ("setting ttl-mc %d", mcast_ttl);
+ g_object_set (G_OBJECT (udpsink), "ttl-mc", mcast_ttl, NULL);
+ }
+ }
+
+
+ /* must be called with lock */
+ static void
+ set_unicast_socket_for_udpsink (GstElement * udpsink, GSocket * socket,
+ GSocketFamily family)
+ {
+ set_socket_for_udpsink (udpsink, socket, family);
+ }
+
+ static guint16
+ get_port_from_socket (GSocket * socket)
+ {
+ guint16 port;
+ GSocketAddress *sockaddr;
+ GError *err;
+
+ GST_DEBUG ("socket: %p", socket);
+ sockaddr = g_socket_get_local_address (socket, &err);
+ if (sockaddr == NULL || !G_IS_INET_SOCKET_ADDRESS (sockaddr)) {
+ g_clear_object (&sockaddr);
+ GST_ERROR ("failed to get sockaddr: %s", err->message);
+ g_error_free (err);
+ return 0;
+ }
+
+ port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr));
+ g_object_unref (sockaddr);
+
+ return port;
+ }
+
+
+ static gboolean
+ create_and_configure_udpsink (GstRTSPStream * stream, GstElement ** udpsink,
+ GSocket * socket_v4, GSocket * socket_v6, gboolean multicast,
+ gboolean is_rtp, gint mcast_ttl)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ *udpsink = gst_element_factory_make ("multiudpsink", NULL);
+
+ if (!*udpsink)
+ goto no_udp_protocol;
+
+ /* configure sinks */
+
+ g_object_set (G_OBJECT (*udpsink), "close-socket", FALSE, NULL);
+
+ g_object_set (G_OBJECT (*udpsink), "send-duplicates", FALSE, NULL);
+
+ if (is_rtp)
+ g_object_set (G_OBJECT (*udpsink), "buffer-size", priv->buffer_size, NULL);
+ else
+ g_object_set (G_OBJECT (*udpsink), "sync", FALSE, NULL);
+
+ /* Needs to be async for RECORD streams, otherwise we will never go to
+ * PLAYING because the sinks will wait for data while the udpsrc can't
+ * provide data with timestamps in PAUSED. */
+ if (!is_rtp || priv->sinkpad)
+ g_object_set (G_OBJECT (*udpsink), "async", FALSE, NULL);
+
+ if (multicast) {
+ /* join multicast group when adding clients, so we'll start receiving from it.
+ * We cannot rely on the udpsrc to join the group since its socket is always a
+ * local unicast one. */
+ g_object_set (G_OBJECT (*udpsink), "auto-multicast", TRUE, NULL);
+
+ g_object_set (G_OBJECT (*udpsink), "loop", FALSE, NULL);
+ }
+
+ /* update the dscp qos field in the sinks */
+ update_dscp_qos (stream, udpsink);
+
+ if (priv->server_addr_v4) {
+ GST_DEBUG_OBJECT (stream, "udp IPv4, configure udpsinks");
+ set_unicast_socket_for_udpsink (*udpsink, socket_v4, G_SOCKET_FAMILY_IPV4);
+ }
+
+ if (priv->server_addr_v6) {
+ GST_DEBUG_OBJECT (stream, "udp IPv6, configure udpsinks");
+ set_unicast_socket_for_udpsink (*udpsink, socket_v6, G_SOCKET_FAMILY_IPV6);
+ }
+
+ if (multicast) {
+ gint port;
+ if (priv->mcast_addr_v4) {
+ GST_DEBUG_OBJECT (stream, "mcast IPv4, configure udpsinks");
+ port = get_port_from_socket (socket_v4);
+ if (!port)
+ goto get_port_failed;
+ set_multicast_socket_for_udpsink (*udpsink, socket_v4,
+ G_SOCKET_FAMILY_IPV4, priv->multicast_iface,
+ priv->mcast_addr_v4->address, port, mcast_ttl);
+ }
+
+ if (priv->mcast_addr_v6) {
+ GST_DEBUG_OBJECT (stream, "mcast IPv6, configure udpsinks");
+ port = get_port_from_socket (socket_v6);
+ if (!port)
+ goto get_port_failed;
+ set_multicast_socket_for_udpsink (*udpsink, socket_v6,
+ G_SOCKET_FAMILY_IPV6, priv->multicast_iface,
+ priv->mcast_addr_v6->address, port, mcast_ttl);
+ }
+
+ }
+
+ return TRUE;
+
+ /* ERRORS */
+ no_udp_protocol:
+ {
+ GST_ERROR_OBJECT (stream, "failed to create udpsink element");
+ return FALSE;
+ }
+ get_port_failed:
+ {
+ GST_ERROR_OBJECT (stream, "failed to get udp port");
+ return FALSE;
+ }
+ }
+
+ /* must be called with lock */
+ static gboolean
+ create_and_configure_udpsource (GstElement ** udpsrc, GSocket * socket)
+ {
+ GstStateChangeReturn ret;
+
+ g_assert (socket != NULL);
+
+ *udpsrc = gst_element_factory_make ("udpsrc", NULL);
+ if (*udpsrc == NULL)
+ goto error;
+
+ g_object_set (G_OBJECT (*udpsrc), "socket", socket, NULL);
+
+ /* The udpsrc cannot do the join because its socket is always a local unicast
+ * one. The udpsink sharing the same socket will do it for us. */
+ g_object_set (G_OBJECT (*udpsrc), "auto-multicast", FALSE, NULL);
+
+ g_object_set (G_OBJECT (*udpsrc), "loop", FALSE, NULL);
+
+ g_object_set (G_OBJECT (*udpsrc), "close-socket", FALSE, NULL);
+
+ ret = gst_element_set_state (*udpsrc, GST_STATE_READY);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto error;
+
+ return TRUE;
+
+ /* ERRORS */
+ error:
+ {
+ if (*udpsrc) {
+ gst_element_set_state (*udpsrc, GST_STATE_NULL);
+ g_clear_object (udpsrc);
+ }
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ alloc_ports_one_family (GstRTSPStream * stream, GSocketFamily family,
+ GSocket * socket_out[2], GstRTSPAddress ** server_addr_out,
+ gboolean multicast, GstRTSPTransport * ct, gboolean use_transport_settings)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GSocket *rtp_socket = NULL;
+ GSocket *rtcp_socket = NULL;
+ gint tmp_rtp, tmp_rtcp;
+ guint count;
+ GList *rejected_addresses = NULL;
+ GstRTSPAddress *addr = NULL;
+ GInetAddress *inetaddr = NULL;
+ GSocketAddress *rtp_sockaddr = NULL;
+ GSocketAddress *rtcp_sockaddr = NULL;
+ GstRTSPAddressPool *pool;
+ gboolean transport_settings_defined = FALSE;
+
+ pool = priv->pool;
+ count = 0;
+
+ /* Start with random port */
+ tmp_rtp = 0;
+ tmp_rtcp = 0;
+
+ if (use_transport_settings) {
+ if (!multicast)
+ goto no_mcast;
+
+ if (ct == NULL)
+ goto no_transport;
+
+ /* multicast and transport specific case */
+ if (ct->destination != NULL) {
+ tmp_rtp = ct->port.min;
+ tmp_rtcp = ct->port.max;
+
+ /* check if the provided address is a multicast address */
+ inetaddr = g_inet_address_new_from_string (ct->destination);
+ if (inetaddr == NULL)
+ goto destination_error;
+ if (!g_inet_address_get_is_multicast (inetaddr))
+ goto destination_no_mcast;
+
+
+ if (!priv->bind_mcast_address) {
+ g_clear_object (&inetaddr);
+ inetaddr = g_inet_address_new_any (family);
+ }
+
+ GST_DEBUG_OBJECT (stream, "use transport settings");
+ transport_settings_defined = TRUE;
+ }
+ }
+
+ if (priv->enable_rtcp) {
+ rtcp_socket = g_socket_new (family, G_SOCKET_TYPE_DATAGRAM,
+ G_SOCKET_PROTOCOL_UDP, NULL);
+ if (!rtcp_socket)
+ goto no_udp_protocol;
+ g_socket_set_multicast_loopback (rtcp_socket, FALSE);
+ }
+
+ /* try to allocate UDP ports, the RTP port should be an even
+ * number and the RTCP port (if enabled) should be the next (uneven) port */
+ again:
+
+ if (rtp_socket == NULL) {
+ rtp_socket = g_socket_new (family, G_SOCKET_TYPE_DATAGRAM,
+ G_SOCKET_PROTOCOL_UDP, NULL);
+ if (!rtp_socket)
+ goto no_udp_protocol;
+ g_socket_set_multicast_loopback (rtp_socket, FALSE);
+ }
+
+ if (!transport_settings_defined) {
+ if ((pool && gst_rtsp_address_pool_has_unicast_addresses (pool))
+ || multicast) {
+ GstRTSPAddressFlags flags;
+
+ if (addr)
+ rejected_addresses = g_list_prepend (rejected_addresses, addr);
+
+ if (!pool)
+ goto no_pool;
+
+ flags = GST_RTSP_ADDRESS_FLAG_EVEN_PORT;
+ if (multicast)
+ flags |= GST_RTSP_ADDRESS_FLAG_MULTICAST;
+ else
+ flags |= GST_RTSP_ADDRESS_FLAG_UNICAST;
+
+ if (family == G_SOCKET_FAMILY_IPV6)
+ flags |= GST_RTSP_ADDRESS_FLAG_IPV6;
+ else
+ flags |= GST_RTSP_ADDRESS_FLAG_IPV4;
+
+ if (*server_addr_out)
+ addr = *server_addr_out;
+ else
+ addr = gst_rtsp_address_pool_acquire_address (pool, flags,
+ priv->enable_rtcp ? 2 : 1);
+
+ if (addr == NULL)
+ goto no_address;
+
+ tmp_rtp = addr->port;
+
+ g_clear_object (&inetaddr);
+ /* FIXME: Does it really work with the IP_MULTICAST_ALL socket option and
+ * socket control message set in udpsrc? */
+ if (priv->bind_mcast_address || !multicast)
+ inetaddr = g_inet_address_new_from_string (addr->address);
+ else
+ inetaddr = g_inet_address_new_any (family);
+ } else {
+ if (tmp_rtp != 0) {
+ tmp_rtp += 2;
+ if (++count > 20)
+ goto no_ports;
+ }
+
+ if (inetaddr == NULL)
+ inetaddr = g_inet_address_new_any (family);
+ }
+ }
+
+ rtp_sockaddr = g_inet_socket_address_new (inetaddr, tmp_rtp);
+ if (!g_socket_bind (rtp_socket, rtp_sockaddr, FALSE, NULL)) {
+ GST_DEBUG_OBJECT (stream, "rtp bind() failed, will try again");
+ g_object_unref (rtp_sockaddr);
+ if (transport_settings_defined)
+ goto transport_settings_error;
+ goto again;
+ }
+ g_object_unref (rtp_sockaddr);
+
+ rtp_sockaddr = g_socket_get_local_address (rtp_socket, NULL);
+ if (rtp_sockaddr == NULL || !G_IS_INET_SOCKET_ADDRESS (rtp_sockaddr)) {
+ g_clear_object (&rtp_sockaddr);
+ goto socket_error;
+ }
+
+ if (!transport_settings_defined) {
+ tmp_rtp =
+ g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (rtp_sockaddr));
+
+ /* check if port is even. RFC 3550 encorages the use of an even/odd port
+ * pair, however it's not a strict requirement so this check is not done
+ * for the client selected ports. */
+ if ((tmp_rtp & 1) != 0) {
+ /* port not even, close and allocate another */
+ tmp_rtp++;
+ g_object_unref (rtp_sockaddr);
+ g_clear_object (&rtp_socket);
+ goto again;
+ }
+ }
+ g_object_unref (rtp_sockaddr);
+
+ /* set port */
+ if (priv->enable_rtcp) {
+ tmp_rtcp = tmp_rtp + 1;
+
+ rtcp_sockaddr = g_inet_socket_address_new (inetaddr, tmp_rtcp);
+ if (!g_socket_bind (rtcp_socket, rtcp_sockaddr, FALSE, NULL)) {
+ GST_DEBUG_OBJECT (stream, "rctp bind() failed, will try again");
+ g_object_unref (rtcp_sockaddr);
+ g_clear_object (&rtp_socket);
+ if (transport_settings_defined)
+ goto transport_settings_error;
+ goto again;
+ }
+ g_object_unref (rtcp_sockaddr);
+ }
+
+ if (!addr) {
+ addr = g_slice_new0 (GstRTSPAddress);
+ addr->port = tmp_rtp;
+ addr->n_ports = 2;
+ if (transport_settings_defined)
+ addr->address = g_strdup (ct->destination);
+ else
+ addr->address = g_inet_address_to_string (inetaddr);
+ addr->ttl = ct->ttl;
+ }
+
+ g_clear_object (&inetaddr);
+
+ if (multicast && (ct->ttl > 0) && (ct->ttl <= priv->max_mcast_ttl)) {
+ GST_DEBUG ("setting mcast ttl to %d", ct->ttl);
+ g_socket_set_multicast_ttl (rtp_socket, ct->ttl);
+ if (rtcp_socket)
+ g_socket_set_multicast_ttl (rtcp_socket, ct->ttl);
+ }
+
+ socket_out[0] = rtp_socket;
+ socket_out[1] = rtcp_socket;
+ *server_addr_out = addr;
+
+ if (priv->enable_rtcp) {
+ GST_DEBUG_OBJECT (stream, "allocated address: %s and ports: %d, %d",
+ addr->address, tmp_rtp, tmp_rtcp);
+ } else {
+ GST_DEBUG_OBJECT (stream, "allocated address: %s and port: %d",
+ addr->address, tmp_rtp);
+ }
+
+ g_list_free_full (rejected_addresses, (GDestroyNotify) gst_rtsp_address_free);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_mcast:
+ {
+ GST_ERROR_OBJECT (stream, "failed to allocate UDP ports: wrong transport");
+ goto cleanup;
+ }
+ no_transport:
+ {
+ GST_ERROR_OBJECT (stream, "failed to allocate UDP ports: no transport");
+ goto cleanup;
+ }
+ destination_error:
+ {
+ GST_ERROR_OBJECT (stream,
+ "failed to allocate UDP ports: destination error");
+ goto cleanup;
+ }
+ destination_no_mcast:
+ {
+ GST_ERROR_OBJECT (stream,
+ "failed to allocate UDP ports: destination not multicast address");
+ goto cleanup;
+ }
+ no_udp_protocol:
+ {
+ GST_WARNING_OBJECT (stream, "failed to allocate UDP ports: protocol error");
+ goto cleanup;
+ }
+ no_pool:
+ {
+ GST_WARNING_OBJECT (stream,
+ "failed to allocate UDP ports: no address pool specified");
+ goto cleanup;
+ }
+ no_address:
+ {
+ GST_WARNING_OBJECT (stream, "failed to acquire address from pool");
+ goto cleanup;
+ }
+ no_ports:
+ {
+ GST_WARNING_OBJECT (stream, "failed to allocate UDP ports: no ports");
+ goto cleanup;
+ }
+ transport_settings_error:
+ {
+ GST_ERROR_OBJECT (stream,
+ "failed to allocate UDP ports with requested transport settings");
+ goto cleanup;
+ }
+ socket_error:
+ {
+ GST_WARNING_OBJECT (stream, "failed to allocate UDP ports: socket error");
+ goto cleanup;
+ }
+ cleanup:
+ {
+ if (inetaddr)
+ g_object_unref (inetaddr);
+ g_list_free_full (rejected_addresses,
+ (GDestroyNotify) gst_rtsp_address_free);
+ if (addr)
+ gst_rtsp_address_free (addr);
+ if (rtp_socket)
+ g_object_unref (rtp_socket);
+ if (rtcp_socket)
+ g_object_unref (rtcp_socket);
+ return FALSE;
+ }
+ }
+
+ /* must be called with lock */
+ static gboolean
+ add_mcast_client_addr (GstRTSPStream * stream, const gchar * destination,
+ guint rtp_port, guint rtcp_port)
+ {
+ GstRTSPStreamPrivate *priv;
+ GList *walk;
+ UdpClientAddrInfo *client;
+ GInetAddress *inet;
+
+ priv = stream->priv;
+
+ if (destination == NULL)
+ return FALSE;
+
+ inet = g_inet_address_new_from_string (destination);
+ if (inet == NULL)
+ goto invalid_address;
+
+ if (!g_inet_address_get_is_multicast (inet)) {
+ g_object_unref (inet);
+ goto invalid_address;
+ }
+ g_object_unref (inet);
+
+ for (walk = priv->mcast_clients; walk; walk = g_list_next (walk)) {
+ UdpClientAddrInfo *cli = walk->data;
+
+ if ((g_strcmp0 (cli->address, destination) == 0) &&
+ (cli->rtp_port == rtp_port)) {
+ GST_DEBUG ("requested destination already exists: %s:%u-%u",
+ destination, rtp_port, rtcp_port);
+ cli->add_count++;
+ return TRUE;
+ }
+ }
+
+ client = g_new0 (UdpClientAddrInfo, 1);
+ client->address = g_strdup (destination);
+ client->rtp_port = rtp_port;
+ client->add_count = 1;
+ priv->mcast_clients = g_list_prepend (priv->mcast_clients, client);
+
+ GST_DEBUG ("added mcast client %s:%u-%u", destination, rtp_port, rtcp_port);
+
+ return TRUE;
+
+ invalid_address:
+ {
+ GST_WARNING_OBJECT (stream, "Multicast address is invalid: %s",
+ destination);
+ return FALSE;
+ }
+ }
+
+ /* must be called with lock */
+ static gboolean
+ remove_mcast_client_addr (GstRTSPStream * stream, const gchar * destination,
+ guint rtp_port, guint rtcp_port)
+ {
+ GstRTSPStreamPrivate *priv;
+ GList *walk;
+
+ priv = stream->priv;
+
+ if (destination == NULL)
+ goto no_destination;
+
+ for (walk = priv->mcast_clients; walk; walk = g_list_next (walk)) {
+ UdpClientAddrInfo *cli = walk->data;
+
+ if ((g_strcmp0 (cli->address, destination) == 0) &&
+ (cli->rtp_port == rtp_port)) {
+ cli->add_count--;
+
+ if (!cli->add_count) {
+ priv->mcast_clients = g_list_remove (priv->mcast_clients, cli);
+ free_mcast_client (cli);
+ }
+ return TRUE;
+ }
+ }
+
+ GST_WARNING_OBJECT (stream, "Address not found");
+ return FALSE;
+
+ no_destination:
+ {
+ GST_WARNING_OBJECT (stream, "No destination has been provided");
+ return FALSE;
+ }
+ }
+
+
+ /**
+ * gst_rtsp_stream_allocate_udp_sockets:
+ * @stream: a #GstRTSPStream
+ * @family: protocol family
+ * @transport: transport method
+ * @use_client_settings: Whether to use client settings or not
+ *
+ * Allocates RTP and RTCP ports.
+ *
+ * Returns: %TRUE if the RTP and RTCP sockets have been succeccully allocated.
+ */
+ gboolean
+ gst_rtsp_stream_allocate_udp_sockets (GstRTSPStream * stream,
+ GSocketFamily family, GstRTSPTransport * ct,
+ gboolean use_transport_settings)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean ret = FALSE;
+ GstRTSPLowerTrans transport;
+ gboolean allocated = FALSE;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ g_return_val_if_fail (ct != NULL, FALSE);
+ priv = stream->priv;
+
+ transport = ct->lower_transport;
+
+ g_mutex_lock (&priv->lock);
+
+ if (transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+ if (family == G_SOCKET_FAMILY_IPV4 && priv->mcast_socket_v4[0])
+ allocated = TRUE;
+ else if (family == G_SOCKET_FAMILY_IPV6 && priv->mcast_socket_v6[0])
+ allocated = TRUE;
+ } else if (transport == GST_RTSP_LOWER_TRANS_UDP) {
+ if (family == G_SOCKET_FAMILY_IPV4 && priv->socket_v4[0])
+ allocated = TRUE;
+ else if (family == G_SOCKET_FAMILY_IPV6 && priv->socket_v6[0])
+ allocated = TRUE;
+ }
+
+ if (allocated) {
+ GST_DEBUG_OBJECT (stream, "Allocated already");
+ g_mutex_unlock (&priv->lock);
+ return TRUE;
+ }
+
+ if (family == G_SOCKET_FAMILY_IPV4) {
+ /* IPv4 */
+ if (transport == GST_RTSP_LOWER_TRANS_UDP) {
+ /* UDP unicast */
+ GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_UDP, ipv4");
+ ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
+ priv->socket_v4, &priv->server_addr_v4, FALSE, ct, FALSE);
+ } else {
+ /* multicast */
+ GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_MCAST_UDP, ipv4");
+ ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV4,
+ priv->mcast_socket_v4, &priv->mcast_addr_v4, TRUE, ct,
+ use_transport_settings);
+ }
+ } else {
+ /* IPv6 */
+ if (transport == GST_RTSP_LOWER_TRANS_UDP) {
+ /* unicast */
+ GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_UDP, ipv6");
+ ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
+ priv->socket_v6, &priv->server_addr_v6, FALSE, ct, FALSE);
+
+ } else {
+ /* multicast */
+ GST_DEBUG_OBJECT (stream, "GST_RTSP_LOWER_TRANS_MCAST_UDP, ipv6");
+ ret = alloc_ports_one_family (stream, G_SOCKET_FAMILY_IPV6,
+ priv->mcast_socket_v6, &priv->mcast_addr_v6, TRUE, ct,
+ use_transport_settings);
+ }
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_set_client_side:
+ * @stream: a #GstRTSPStream
+ * @client_side: TRUE if this #GstRTSPStream is running on the 'client' side of
+ * an RTSP connection.
+ *
+ * Sets the #GstRTSPStream as a 'client side' stream - used for sending
+ * streams to an RTSP server via RECORD. This has the practical effect
+ * of changing which UDP port numbers are used when setting up the local
+ * side of the stream sending to be either the 'server' or 'client' pair
+ * of a configured UDP transport.
+ */
+ void
+ gst_rtsp_stream_set_client_side (GstRTSPStream * stream, gboolean client_side)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ priv->client_side = client_side;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_is_client_side:
+ * @stream: a #GstRTSPStream
+ *
+ * See gst_rtsp_stream_set_client_side()
+ *
+ * Returns: TRUE if this #GstRTSPStream is client-side.
+ */
+ gboolean
+ gst_rtsp_stream_is_client_side (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean ret;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ ret = priv->client_side;
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_get_server_port:
+ * @stream: a #GstRTSPStream
+ * @server_port: (out): result server port
+ * @family: the port family to get
+ *
+ * Fill @server_port with the port pair used by the server. This function can
+ * only be called when @stream has been joined.
+ */
+ void
+ gst_rtsp_stream_get_server_port (GstRTSPStream * stream,
+ GstRTSPRange * server_port, GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+ priv = stream->priv;
+ g_return_if_fail (priv->joined_bin != NULL);
+
+ if (server_port) {
+ server_port->min = 0;
+ server_port->max = 0;
+ }
+
+ g_mutex_lock (&priv->lock);
+ if (family == G_SOCKET_FAMILY_IPV4) {
+ if (server_port && priv->server_addr_v4) {
+ server_port->min = priv->server_addr_v4->port;
+ if (priv->enable_rtcp) {
+ server_port->max =
+ priv->server_addr_v4->port + priv->server_addr_v4->n_ports - 1;
+ }
+ }
+ } else {
+ if (server_port && priv->server_addr_v6) {
+ server_port->min = priv->server_addr_v6->port;
+ if (priv->enable_rtcp) {
+ server_port->max =
+ priv->server_addr_v6->port + priv->server_addr_v6->n_ports - 1;
+ }
+ }
+ }
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_rtpsession:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the RTP session of this stream.
+ *
+ * Returns: (transfer full): The RTP session of this stream. Unref after usage.
+ */
+ GObject *
+ gst_rtsp_stream_get_rtpsession (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GObject *session;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((session = priv->session))
+ g_object_ref (session);
+ g_mutex_unlock (&priv->lock);
+
+ return session;
+ }
+
+ /**
+ * gst_rtsp_stream_get_srtp_encoder:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the SRTP encoder for this stream.
+ *
+ * Returns: (transfer full): The SRTP encoder for this stream. Unref after usage.
+ */
+ GstElement *
+ gst_rtsp_stream_get_srtp_encoder (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstElement *encoder;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((encoder = priv->srtpenc))
+ g_object_ref (encoder);
+ g_mutex_unlock (&priv->lock);
+
+ return encoder;
+ }
+
+ /**
+ * gst_rtsp_stream_get_ssrc:
+ * @stream: a #GstRTSPStream
+ * @ssrc: (out): result ssrc
+ *
+ * Get the SSRC used by the RTP session of this stream. This function can only
+ * be called when @stream has been joined.
+ */
+ void
+ gst_rtsp_stream_get_ssrc (GstRTSPStream * stream, guint * ssrc)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+ priv = stream->priv;
+ g_return_if_fail (priv->joined_bin != NULL);
+
+ g_mutex_lock (&priv->lock);
+ if (ssrc && priv->session)
+ g_object_get (priv->session, "internal-ssrc", ssrc, NULL);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_set_retransmission_time:
+ * @stream: a #GstRTSPStream
+ * @time: a #GstClockTime
+ *
+ * Set the amount of time to store retransmission packets.
+ */
+ void
+ gst_rtsp_stream_set_retransmission_time (GstRTSPStream * stream,
+ GstClockTime time)
+ {
+ GST_DEBUG_OBJECT (stream, "set retransmission time %" G_GUINT64_FORMAT, time);
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->rtx_time = time;
+ if (stream->priv->rtxsend)
+ g_object_set (stream->priv->rtxsend, "max-size-time",
+ GST_TIME_AS_MSECONDS (time), NULL);
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_retransmission_time:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the amount of time to store retransmission data.
+ *
+ * Returns: the amount of time to store retransmission data.
+ */
+ GstClockTime
+ gst_rtsp_stream_get_retransmission_time (GstRTSPStream * stream)
+ {
+ GstClockTime ret;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);
+
+ g_mutex_lock (&stream->priv->lock);
+ ret = stream->priv->rtx_time;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_set_retransmission_pt:
+ * @stream: a #GstRTSPStream
+ * @rtx_pt: a #guint
+ *
+ * Set the payload type (pt) for retransmission of this stream.
+ */
+ void
+ gst_rtsp_stream_set_retransmission_pt (GstRTSPStream * stream, guint rtx_pt)
+ {
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ GST_DEBUG_OBJECT (stream, "set retransmission pt %u", rtx_pt);
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->rtx_pt = rtx_pt;
+ if (stream->priv->rtxsend) {
+ guint pt = gst_rtsp_stream_get_pt (stream);
+ gchar *pt_s = g_strdup_printf ("%d", pt);
+ GstStructure *rtx_pt_map = gst_structure_new ("application/x-rtp-pt-map",
+ pt_s, G_TYPE_UINT, rtx_pt, NULL);
+ g_object_set (stream->priv->rtxsend, "payload-type-map", rtx_pt_map, NULL);
+ g_free (pt_s);
+ gst_structure_free (rtx_pt_map);
+ }
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_retransmission_pt:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the payload-type used for retransmission of this stream
+ *
+ * Returns: The retransmission PT.
+ */
+ guint
+ gst_rtsp_stream_get_retransmission_pt (GstRTSPStream * stream)
+ {
+ guint rtx_pt;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);
+
+ g_mutex_lock (&stream->priv->lock);
+ rtx_pt = stream->priv->rtx_pt;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return rtx_pt;
+ }
+
+ /**
+ * gst_rtsp_stream_set_buffer_size:
+ * @stream: a #GstRTSPStream
+ * @size: the buffer size
+ *
+ * Set the size of the UDP transmission buffer (in bytes)
+ * Needs to be set before the stream is joined to a bin.
+ *
+ * Since: 1.6
+ */
+ void
+ gst_rtsp_stream_set_buffer_size (GstRTSPStream * stream, guint size)
+ {
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->buffer_size = size;
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_buffer_size:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the size of the UDP transmission buffer (in bytes)
+ *
+ * Returns: the size of the UDP TX buffer
+ *
+ * Since: 1.6
+ */
+ guint
+ gst_rtsp_stream_get_buffer_size (GstRTSPStream * stream)
+ {
+ guint buffer_size;
+
+ g_mutex_lock (&stream->priv->lock);
+ buffer_size = stream->priv->buffer_size;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return buffer_size;
+ }
+
+ /**
+ * gst_rtsp_stream_set_max_mcast_ttl:
+ * @stream: a #GstRTSPStream
+ * @ttl: the new multicast ttl value
+ *
+ * Set the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Returns: %TRUE if the requested ttl has been set successfully.
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_stream_set_max_mcast_ttl (GstRTSPStream * stream, guint ttl)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ g_mutex_lock (&stream->priv->lock);
+ if (ttl == 0 || ttl > DEFAULT_MAX_MCAST_TTL) {
+ GST_WARNING_OBJECT (stream, "The reqested mcast TTL value is not valid.");
+ g_mutex_unlock (&stream->priv->lock);
+ return FALSE;
+ }
+ stream->priv->max_mcast_ttl = ttl;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_stream_get_max_mcast_ttl:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Returns: the maximum time-to-live value of outgoing multicast packets.
+ *
+ * Since: 1.16
+ */
+ guint
+ gst_rtsp_stream_get_max_mcast_ttl (GstRTSPStream * stream)
+ {
+ guint ttl;
+
+ g_mutex_lock (&stream->priv->lock);
+ ttl = stream->priv->max_mcast_ttl;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return ttl;
+ }
+
+ /**
+ * gst_rtsp_stream_verify_mcast_ttl:
+ * @stream: a #GstRTSPStream
+ * @ttl: a requested multicast ttl
+ *
+ * Check if the requested multicast ttl value is allowed.
+ *
+ * Returns: TRUE if the requested ttl value is allowed.
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_stream_verify_mcast_ttl (GstRTSPStream * stream, guint ttl)
+ {
+ gboolean res = FALSE;
+
+ g_mutex_lock (&stream->priv->lock);
+ if ((ttl > 0) && (ttl <= stream->priv->max_mcast_ttl))
+ res = TRUE;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_set_bind_mcast_address:
+ * @stream: a #GstRTSPStream,
+ * @bind_mcast_addr: the new value
+ *
+ * Decide whether the multicast socket should be bound to a multicast address or
+ * INADDR_ANY.
+ *
+ * Since: 1.16
+ */
+ void
+ gst_rtsp_stream_set_bind_mcast_address (GstRTSPStream * stream,
+ gboolean bind_mcast_addr)
+ {
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->bind_mcast_address = bind_mcast_addr;
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_is_bind_mcast_address:
+ * @stream: a #GstRTSPStream
+ *
+ * Check if multicast sockets are configured to be bound to multicast addresses.
+ *
+ * Returns: %TRUE if multicast sockets are configured to be bound to multicast addresses.
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_stream_is_bind_mcast_address (GstRTSPStream * stream)
+ {
+ gboolean result;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ g_mutex_lock (&stream->priv->lock);
+ result = stream->priv->bind_mcast_address;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return result;
+ }
+
+ void
+ gst_rtsp_stream_set_enable_rtcp (GstRTSPStream * stream, gboolean enable)
+ {
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->enable_rtcp = enable;
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /* executed from streaming thread */
+ static void
+ caps_notify (GstPad * pad, GParamSpec * unused, GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstCaps *newcaps, *oldcaps;
+
+ newcaps = gst_pad_get_current_caps (pad);
+
+ GST_INFO ("stream %p received caps %p, %" GST_PTR_FORMAT, stream, newcaps,
+ newcaps);
+
+ g_mutex_lock (&priv->lock);
+ oldcaps = priv->caps;
+ priv->caps = newcaps;
+ g_mutex_unlock (&priv->lock);
+
+ if (oldcaps)
+ gst_caps_unref (oldcaps);
+ }
+
+ static void
+ dump_structure (const GstStructure * s)
+ {
+ gchar *sstr;
+
+ sstr = gst_structure_to_string (s);
+ GST_INFO ("structure: %s", sstr);
+ g_free (sstr);
+ }
+
+ static GstRTSPStreamTransport *
+ find_transport (GstRTSPStream * stream, const gchar * rtcp_from)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GList *walk;
+ GstRTSPStreamTransport *result = NULL;
+ const gchar *tmp;
+ gchar *dest;
+ guint port;
+
+ if (rtcp_from == NULL)
+ return NULL;
+
+ tmp = g_strrstr (rtcp_from, ":");
+ if (tmp == NULL)
+ return NULL;
+
+ port = atoi (tmp + 1);
+ dest = g_strndup (rtcp_from, tmp - rtcp_from);
+
+ g_mutex_lock (&priv->lock);
+ GST_INFO ("finding %s:%d in %d transports", dest, port,
+ g_list_length (priv->transports));
+
+ for (walk = priv->transports; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamTransport *trans = walk->data;
+ const GstRTSPTransport *tr;
+ gint min, max;
+
+ tr = gst_rtsp_stream_transport_get_transport (trans);
+
+ if (priv->client_side) {
+ /* In client side mode the 'destination' is the RTSP server, so send
+ * to those ports */
+ min = tr->server_port.min;
+ max = tr->server_port.max;
+ } else {
+ min = tr->client_port.min;
+ max = tr->client_port.max;
+ }
+
+ if ((g_ascii_strcasecmp (tr->destination, dest) == 0) &&
+ (min == port || max == port)) {
+ result = trans;
+ break;
+ }
+ }
+ if (result)
+ g_object_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ g_free (dest);
+
+ return result;
+ }
+
+ static GstRTSPStreamTransport *
+ check_transport (GObject * source, GstRTSPStream * stream)
+ {
+ GstStructure *stats;
+ GstRTSPStreamTransport *trans;
+
+ /* see if we have a stream to match with the origin of the RTCP packet */
+ trans = g_object_get_qdata (source, ssrc_stream_map_key);
+ if (trans == NULL) {
+ g_object_get (source, "stats", &stats, NULL);
+ if (stats) {
+ const gchar *rtcp_from;
+
+ dump_structure (stats);
+
++ g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_RTCP_STATS], 0,
++ stats);
++
+ rtcp_from = gst_structure_get_string (stats, "rtcp-from");
+ if ((trans = find_transport (stream, rtcp_from))) {
+ GST_INFO ("%p: found transport %p for source %p", stream, trans,
+ source);
+ g_object_set_qdata_full (source, ssrc_stream_map_key, trans,
+ g_object_unref);
+ }
+ gst_structure_free (stats);
+ }
+ }
+ return trans;
+ }
+
+
+ static void
+ on_new_ssrc (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GstRTSPStreamTransport *trans;
+
+ GST_INFO ("%p: new source %p", stream, source);
+
+ trans = check_transport (source, stream);
+
+ if (trans)
+ GST_INFO ("%p: source %p for transport %p", stream, source, trans);
+ }
+
+ static void
+ on_ssrc_sdes (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GST_INFO ("%p: new SDES %p", stream, source);
+ }
+
+ static void
+ on_ssrc_active (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GstRTSPStreamTransport *trans;
+
+ trans = check_transport (source, stream);
+
+ if (trans) {
+ GST_INFO ("%p: source %p in transport %p is active", stream, source, trans);
+ gst_rtsp_stream_transport_keep_alive (trans);
+ }
+ #ifdef DUMP_STATS
+ {
+ GstStructure *stats;
+ g_object_get (source, "stats", &stats, NULL);
+ if (stats) {
++ g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_RTCP_STATS], 0,
++ stats);
++
+ dump_structure (stats);
+ gst_structure_free (stats);
+ }
+ }
+ #endif
+ }
+
+ static void
+ on_bye_ssrc (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GST_INFO ("%p: source %p bye", stream, source);
+ }
+
+ static void
+ on_bye_timeout (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GstRTSPStreamTransport *trans;
+
+ GST_INFO ("%p: source %p bye timeout", stream, source);
+
+ if ((trans = g_object_get_qdata (source, ssrc_stream_map_key))) {
+ gst_rtsp_stream_transport_set_timed_out (trans, TRUE);
+ g_object_set_qdata (source, ssrc_stream_map_key, NULL);
+ }
+ }
+
+ static void
+ on_timeout (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GstRTSPStreamTransport *trans;
+
+ GST_INFO ("%p: source %p timeout", stream, source);
+
+ if ((trans = g_object_get_qdata (source, ssrc_stream_map_key))) {
+ gst_rtsp_stream_transport_set_timed_out (trans, TRUE);
+ g_object_set_qdata (source, ssrc_stream_map_key, NULL);
+ }
+ }
+
+ static void
+ on_new_sender_ssrc (GObject * session, GObject * source, GstRTSPStream * stream)
+ {
+ GST_INFO ("%p: new sender source %p", stream, source);
+ #ifndef DUMP_STATS
+ {
+ GstStructure *stats;
+ g_object_get (source, "stats", &stats, NULL);
+ if (stats) {
+ dump_structure (stats);
+ gst_structure_free (stats);
+ }
+ }
+ #endif
+ }
+
+ static void
+ on_sender_ssrc_active (GObject * session, GObject * source,
+ GstRTSPStream * stream)
+ {
+ #ifndef DUMP_STATS
+ {
+ GstStructure *stats;
+ g_object_get (source, "stats", &stats, NULL);
+ if (stats) {
+ dump_structure (stats);
+ gst_structure_free (stats);
+ }
+ }
+ #endif
+ }
+
+ static void
+ clear_tr_cache (GstRTSPStreamPrivate * priv)
+ {
+ if (priv->tr_cache)
+ g_ptr_array_unref (priv->tr_cache);
+ priv->tr_cache = NULL;
+ }
+
+ /* With lock taken */
+ static gboolean
+ any_transport_ready (GstRTSPStream * stream, gboolean is_rtp)
+ {
+ gboolean ret = TRUE;
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GPtrArray *transports;
+ gint index;
+
+ transports = priv->tr_cache;
+
+ if (!transports)
+ goto done;
+
+ for (index = 0; index < transports->len; index++) {
+ GstRTSPStreamTransport *tr = g_ptr_array_index (transports, index);
+ if (!gst_rtsp_stream_transport_check_back_pressure (tr, is_rtp)) {
+ ret = TRUE;
+ break;
+ } else {
+ ret = FALSE;
+ }
+ }
+
+ done:
+ return ret;
+ }
+
+ /* Must be called *without* priv->lock */
+ static gboolean
+ push_data (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
+ GstBuffer * buffer, GstBufferList * buffer_list, gboolean is_rtp)
+ {
+ gboolean send_ret = TRUE;
+
+ if (is_rtp) {
+ if (buffer)
+ send_ret = gst_rtsp_stream_transport_send_rtp (trans, buffer);
+ if (buffer_list)
+ send_ret = gst_rtsp_stream_transport_send_rtp_list (trans, buffer_list);
+ } else {
+ if (buffer)
+ send_ret = gst_rtsp_stream_transport_send_rtcp (trans, buffer);
+ if (buffer_list)
+ send_ret = gst_rtsp_stream_transport_send_rtcp_list (trans, buffer_list);
+ }
+
+ return send_ret;
+ }
+
+ /* With priv->lock */
+ static void
+ ensure_cached_transports (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GList *walk;
+
+ if (priv->tr_cache_cookie != priv->transports_cookie) {
+ clear_tr_cache (priv);
+ priv->tr_cache =
+ g_ptr_array_new_full (priv->n_tcp_transports, g_object_unref);
+
+ for (walk = priv->transports; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamTransport *tr = (GstRTSPStreamTransport *) walk->data;
+ const GstRTSPTransport *t = gst_rtsp_stream_transport_get_transport (tr);
+
+ if (t->lower_transport != GST_RTSP_LOWER_TRANS_TCP)
+ continue;
+
+ g_ptr_array_add (priv->tr_cache, g_object_ref (tr));
+ }
+ priv->tr_cache_cookie = priv->transports_cookie;
+ }
+ }
+
+ /* Must be called *without* priv->lock */
+ static void
+ check_transport_backlog (GstRTSPStream * stream, GstRTSPStreamTransport * trans)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ gboolean send_ret = TRUE;
+
+ gst_rtsp_stream_transport_lock_backlog (trans);
+
+ if (!gst_rtsp_stream_transport_backlog_is_empty (trans)) {
+ GstBuffer *buffer;
+ GstBufferList *buffer_list;
+ gboolean is_rtp;
+ gboolean popped;
+
+ popped =
+ gst_rtsp_stream_transport_backlog_pop (trans, &buffer, &buffer_list,
+ &is_rtp);
+
+ g_assert (popped == TRUE);
+
+ send_ret = push_data (stream, trans, buffer, buffer_list, is_rtp);
+
+ gst_clear_buffer (&buffer);
+ gst_clear_buffer_list (&buffer_list);
+ }
+
+ gst_rtsp_stream_transport_unlock_backlog (trans);
+
+ if (!send_ret) {
+ /* remove transport on send error */
+ g_mutex_lock (&priv->lock);
+ update_transport (stream, trans, FALSE);
+ g_mutex_unlock (&priv->lock);
+ }
+ }
+
+ /* Must be called with priv->lock */
+ static void
+ send_tcp_message (GstRTSPStream * stream, gint idx)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstAppSink *sink;
+ GstSample *sample;
+ GstBuffer *buffer;
+ GstBufferList *buffer_list;
+ guint n_messages = 0;
+ gboolean is_rtp;
+ GPtrArray *transports;
+
+ if (!priv->have_buffer[idx])
+ return;
+
+ ensure_cached_transports (stream);
+
+ is_rtp = (idx == 0);
+
+ if (!any_transport_ready (stream, is_rtp))
+ return;
+
+ priv->have_buffer[idx] = FALSE;
+
+ if (priv->appsink[idx] == NULL) {
+ /* session expired */
+ return;
+ }
+
+ sink = GST_APP_SINK (priv->appsink[idx]);
+ sample = gst_app_sink_pull_sample (sink);
+ if (!sample) {
+ return;
+ }
+
+ buffer = gst_sample_get_buffer (sample);
+ buffer_list = gst_sample_get_buffer_list (sample);
+
+ /* We will get one message-sent notification per buffer or
+ * complete buffer-list. We handle each buffer-list as a unit */
+ if (buffer)
+ n_messages += 1;
+ if (buffer_list)
+ n_messages += 1;
+
+ transports = priv->tr_cache;
+ if (transports)
+ g_ptr_array_ref (transports);
+
+ if (transports) {
+ gint index;
+
+ for (index = 0; index < transports->len; index++) {
+ GstRTSPStreamTransport *tr = g_ptr_array_index (transports, index);
+ GstBuffer *buf_ref = NULL;
+ GstBufferList *buflist_ref = NULL;
+
+ gst_rtsp_stream_transport_lock_backlog (tr);
+
+ if (buffer)
+ buf_ref = gst_buffer_ref (buffer);
+ if (buffer_list)
+ buflist_ref = gst_buffer_list_ref (buffer_list);
+
+ if (!gst_rtsp_stream_transport_backlog_push (tr,
+ buf_ref, buflist_ref, is_rtp)) {
+ GST_ERROR_OBJECT (stream,
+ "Dropping slow transport %" GST_PTR_FORMAT, tr);
+ update_transport (stream, tr, FALSE);
+ }
+
+ gst_rtsp_stream_transport_unlock_backlog (tr);
+ }
+ }
+ gst_sample_unref (sample);
+
+ g_mutex_unlock (&priv->lock);
+
+ if (transports) {
+ gint index;
+
+ for (index = 0; index < transports->len; index++) {
+ GstRTSPStreamTransport *tr = g_ptr_array_index (transports, index);
+
+ check_transport_backlog (stream, tr);
+ }
+ g_ptr_array_unref (transports);
+ }
+
+ g_mutex_lock (&priv->lock);
+ }
+
+ static gpointer
+ send_func (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ g_mutex_lock (&priv->send_lock);
+
+ while (priv->continue_sending) {
+ int i;
+ int idx = -1;
+ guint cookie;
+
+ cookie = priv->send_cookie;
+ g_mutex_unlock (&priv->send_lock);
+
+ g_mutex_lock (&priv->lock);
+
+ /* iterate from 1 and down, so we prioritize RTCP over RTP */
+ for (i = 1; i >= 0; i--) {
+ if (priv->have_buffer[i]) {
+ /* send message */
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx != -1) {
+ send_tcp_message (stream, idx);
+ }
+
+ g_mutex_unlock (&priv->lock);
+
+ g_mutex_lock (&priv->send_lock);
+ while (cookie == priv->send_cookie && priv->continue_sending) {
+ g_cond_wait (&priv->send_cond, &priv->send_lock);
+ }
+ }
+
+ g_mutex_unlock (&priv->send_lock);
+
+ return NULL;
+ }
+
+ static GstFlowReturn
+ handle_new_sample (GstAppSink * sink, gpointer user_data)
+ {
+ GstRTSPStream *stream = user_data;
+ GstRTSPStreamPrivate *priv = stream->priv;
+ int i;
+
+ g_mutex_lock (&priv->lock);
+
+ for (i = 0; i < 2; i++) {
+ if (GST_ELEMENT_CAST (sink) == priv->appsink[i]) {
+ priv->have_buffer[i] = TRUE;
+ break;
+ }
+ }
+
+ if (priv->send_thread == NULL) {
+ priv->send_thread = g_thread_new (NULL, (GThreadFunc) send_func, user_data);
+ }
+
+ g_mutex_unlock (&priv->lock);
+
+ g_mutex_lock (&priv->send_lock);
+ priv->send_cookie++;
+ g_cond_signal (&priv->send_cond);
+ g_mutex_unlock (&priv->send_lock);
+
+ return GST_FLOW_OK;
+ }
+
+ static GstAppSinkCallbacks sink_cb = {
+ NULL, /* not interested in EOS */
+ NULL, /* not interested in preroll samples */
+ handle_new_sample,
+ };
+
+ static GstElement *
+ get_rtp_encoder (GstRTSPStream * stream, guint session)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ if (priv->srtpenc == NULL) {
+ gchar *name;
+
+ name = g_strdup_printf ("srtpenc_%u", session);
+ priv->srtpenc = gst_element_factory_make ("srtpenc", name);
+ g_free (name);
+
+ g_object_set (priv->srtpenc, "random-key", TRUE, NULL);
+ }
+ return gst_object_ref (priv->srtpenc);
+ }
+
+ static GstElement *
+ request_rtp_encoder (GstElement * rtpbin, guint session, GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstElement *oldenc, *enc;
+ GstPad *pad;
+ gchar *name;
+
+ if (priv->idx != session)
+ return NULL;
+
+ GST_DEBUG_OBJECT (stream, "make RTP encoder for session %u", session);
+
+ oldenc = priv->srtpenc;
+ enc = get_rtp_encoder (stream, session);
+ name = g_strdup_printf ("rtp_sink_%d", session);
+ pad = gst_element_request_pad_simple (enc, name);
+ g_free (name);
+ gst_object_unref (pad);
+
+ if (oldenc == NULL)
+ g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_NEW_RTP_ENCODER], 0,
+ enc);
+
+ return enc;
+ }
+
+ static GstElement *
+ request_rtcp_encoder (GstElement * rtpbin, guint session,
+ GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstElement *oldenc, *enc;
+ GstPad *pad;
+ gchar *name;
+
+ if (priv->idx != session)
+ return NULL;
+
+ GST_DEBUG_OBJECT (stream, "make RTCP encoder for session %u", session);
+
+ oldenc = priv->srtpenc;
+ enc = get_rtp_encoder (stream, session);
+ name = g_strdup_printf ("rtcp_sink_%d", session);
+ pad = gst_element_request_pad_simple (enc, name);
+ g_free (name);
+ gst_object_unref (pad);
+
+ if (oldenc == NULL)
+ g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_NEW_RTCP_ENCODER], 0,
+ enc);
+
+ return enc;
+ }
+
+ static GstCaps *
+ request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstCaps *caps;
+
+ GST_DEBUG ("request key %08x", ssrc);
+
+ g_mutex_lock (&priv->lock);
+ if ((caps = g_hash_table_lookup (priv->keys, GINT_TO_POINTER (ssrc))))
+ gst_caps_ref (caps);
+ g_mutex_unlock (&priv->lock);
+
+ return caps;
+ }
+
+ static GstElement *
+ request_rtp_rtcp_decoder (GstElement * rtpbin, guint session,
+ GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ if (priv->idx != session)
+ return NULL;
+
+ if (priv->srtpdec == NULL) {
+ gchar *name;
+
+ name = g_strdup_printf ("srtpdec_%u", session);
+ priv->srtpdec = gst_element_factory_make ("srtpdec", name);
+ g_free (name);
+
+ g_signal_connect (priv->srtpdec, "request-key",
+ (GCallback) request_key, stream);
+
+ g_signal_emit (stream, gst_rtsp_stream_signals[SIGNAL_NEW_RTP_RTCP_DECODER],
+ 0, priv->srtpdec);
+
+ }
+ return gst_object_ref (priv->srtpdec);
+ }
+
+ /**
+ * gst_rtsp_stream_request_aux_sender:
+ * @stream: a #GstRTSPStream
+ * @sessid: the session id
+ *
+ * Creating a rtxsend bin
+ *
+ * Returns: (transfer full) (nullable): a #GstElement.
+ *
+ * Since: 1.6
+ */
+ GstElement *
+ gst_rtsp_stream_request_aux_sender (GstRTSPStream * stream, guint sessid)
+ {
+ GstElement *bin;
+ GstPad *pad;
+ GstStructure *pt_map;
+ gchar *name;
+ guint pt, rtx_pt;
+ gchar *pt_s;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ pt = gst_rtsp_stream_get_pt (stream);
+ pt_s = g_strdup_printf ("%u", pt);
+ rtx_pt = stream->priv->rtx_pt;
+
+ GST_INFO ("creating rtxsend with pt %u to %u", pt, rtx_pt);
+
+ bin = gst_bin_new (NULL);
+ stream->priv->rtxsend = gst_element_factory_make ("rtprtxsend", NULL);
+ pt_map = gst_structure_new ("application/x-rtp-pt-map",
+ pt_s, G_TYPE_UINT, rtx_pt, NULL);
+ g_object_set (stream->priv->rtxsend, "payload-type-map", pt_map,
+ "max-size-time", GST_TIME_AS_MSECONDS (stream->priv->rtx_time), NULL);
+ g_free (pt_s);
+ gst_structure_free (pt_map);
+ gst_bin_add (GST_BIN (bin), gst_object_ref (stream->priv->rtxsend));
+
+ pad = gst_element_get_static_pad (stream->priv->rtxsend, "src");
+ name = g_strdup_printf ("src_%u", sessid);
+ gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
+ g_free (name);
+ gst_object_unref (pad);
+
+ pad = gst_element_get_static_pad (stream->priv->rtxsend, "sink");
+ name = g_strdup_printf ("sink_%u", sessid);
+ gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
+ g_free (name);
+ gst_object_unref (pad);
+
+ return bin;
+ }
+
+ static void
+ add_rtx_pt (gpointer key, GstCaps * caps, GstStructure * pt_map)
+ {
+ guint pt = GPOINTER_TO_INT (key);
+ const GstStructure *s = gst_caps_get_structure (caps, 0);
+ const gchar *apt;
+
+ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "RTX") &&
+ (apt = gst_structure_get_string (s, "apt"))) {
+ gst_structure_set (pt_map, apt, G_TYPE_UINT, pt, NULL);
+ }
+ }
+
+ /* Call with priv->lock taken */
+ static void
+ update_rtx_receive_pt_map (GstRTSPStream * stream)
+ {
+ GstStructure *pt_map;
+
+ if (!stream->priv->rtxreceive)
+ goto done;
+
+ pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+ g_hash_table_foreach (stream->priv->ptmap, (GHFunc) add_rtx_pt, pt_map);
+ g_object_set (stream->priv->rtxreceive, "payload-type-map", pt_map, NULL);
+ gst_structure_free (pt_map);
+
+ done:
+ return;
+ }
+
+ static void
+ retrieve_ulpfec_pt (gpointer key, GstCaps * caps, GstElement * ulpfec_decoder)
+ {
+ guint pt = GPOINTER_TO_INT (key);
+ const GstStructure *s = gst_caps_get_structure (caps, 0);
+
+ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "ULPFEC"))
+ g_object_set (ulpfec_decoder, "pt", pt, NULL);
+ }
+
+ static void
+ update_ulpfec_decoder_pt (GstRTSPStream * stream)
+ {
+ if (!stream->priv->ulpfec_decoder)
+ goto done;
+
+ g_hash_table_foreach (stream->priv->ptmap, (GHFunc) retrieve_ulpfec_pt,
+ stream->priv->ulpfec_decoder);
+
+ done:
+ return;
+ }
+
+ /**
+ * gst_rtsp_stream_request_aux_receiver:
+ * @stream: a #GstRTSPStream
+ * @sessid: the session id
+ *
+ * Creating a rtxreceive bin
+ *
+ * Returns: (transfer full) (nullable): a #GstElement.
+ *
+ * Since: 1.16
+ */
+ GstElement *
+ gst_rtsp_stream_request_aux_receiver (GstRTSPStream * stream, guint sessid)
+ {
+ GstElement *bin;
+ GstPad *pad;
+ gchar *name;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ bin = gst_bin_new (NULL);
+ stream->priv->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
+ update_rtx_receive_pt_map (stream);
+ update_ulpfec_decoder_pt (stream);
+ gst_bin_add (GST_BIN (bin), gst_object_ref (stream->priv->rtxreceive));
+
+ pad = gst_element_get_static_pad (stream->priv->rtxreceive, "src");
+ name = g_strdup_printf ("src_%u", sessid);
+ gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
+ g_free (name);
+ gst_object_unref (pad);
+
+ pad = gst_element_get_static_pad (stream->priv->rtxreceive, "sink");
+ name = g_strdup_printf ("sink_%u", sessid);
+ gst_element_add_pad (bin, gst_ghost_pad_new (name, pad));
+ g_free (name);
+ gst_object_unref (pad);
+
+ return bin;
+ }
+
+ /**
+ * gst_rtsp_stream_set_pt_map:
+ * @stream: a #GstRTSPStream
+ * @pt: the pt
+ * @caps: a #GstCaps
+ *
+ * Configure a pt map between @pt and @caps.
+ */
+ void
+ gst_rtsp_stream_set_pt_map (GstRTSPStream * stream, guint pt, GstCaps * caps)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ if (!GST_IS_CAPS (caps))
+ return;
+
+ g_mutex_lock (&priv->lock);
+ g_hash_table_insert (priv->ptmap, GINT_TO_POINTER (pt), gst_caps_ref (caps));
+ update_rtx_receive_pt_map (stream);
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_set_publish_clock_mode:
+ * @stream: a #GstRTSPStream
+ * @mode: the clock publish mode
+ *
+ * Sets if and how the stream clock should be published according to RFC7273.
+ *
+ * Since: 1.8
+ */
+ void
+ gst_rtsp_stream_set_publish_clock_mode (GstRTSPStream * stream,
+ GstRTSPPublishClockMode mode)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ priv->publish_clock_mode = mode;
+ g_mutex_unlock (&priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_publish_clock_mode:
+ * @stream: a #GstRTSPStream
+ *
+ * Gets if and how the stream clock should be published according to RFC7273.
+ *
+ * Returns: The GstRTSPPublishClockMode
+ *
+ * Since: 1.8
+ */
+ GstRTSPPublishClockMode
+ gst_rtsp_stream_get_publish_clock_mode (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPPublishClockMode ret;
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ ret = priv->publish_clock_mode;
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ static GstCaps *
+ request_pt_map (GstElement * rtpbin, guint session, guint pt,
+ GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstCaps *caps = NULL;
+
+ g_mutex_lock (&priv->lock);
+
+ if (priv->idx == session) {
+ caps = g_hash_table_lookup (priv->ptmap, GINT_TO_POINTER (pt));
+ if (caps) {
+ GST_DEBUG ("Stream %p, pt %u: caps %" GST_PTR_FORMAT, stream, pt, caps);
+ gst_caps_ref (caps);
+ } else {
+ GST_DEBUG ("Stream %p, pt %u: no caps", stream, pt);
+ }
+ }
+
+ g_mutex_unlock (&priv->lock);
+
+ return caps;
+ }
+
+ static void
+ pad_added (GstElement * rtpbin, GstPad * pad, GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ gchar *name;
+ GstPadLinkReturn ret;
+ guint sessid;
+
+ GST_DEBUG ("Stream %p added pad %s:%s for pad %s:%s", stream,
+ GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->sinkpad));
+
+ name = gst_pad_get_name (pad);
+ if (sscanf (name, "recv_rtp_src_%u", &sessid) != 1) {
+ g_free (name);
+ return;
+ }
+ g_free (name);
+
+ if (priv->idx != sessid)
+ return;
+
+ if (gst_pad_is_linked (priv->sinkpad)) {
+ GST_WARNING ("Stream %p: Pad %s:%s is linked already", stream,
+ GST_DEBUG_PAD_NAME (priv->sinkpad));
+ return;
+ }
+
+ /* link the RTP pad to the session manager, it should not really fail unless
+ * this is not really an RTP pad */
+ ret = gst_pad_link (pad, priv->sinkpad);
+ if (ret != GST_PAD_LINK_OK)
+ goto link_failed;
+ priv->recv_rtp_src = gst_object_ref (pad);
+
+ return;
+
+ /* ERRORS */
+ link_failed:
+ {
+ GST_ERROR ("Stream %p: Failed to link pads %s:%s and %s:%s", stream,
+ GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (priv->sinkpad));
+ }
+ }
+
+ static void
+ on_npt_stop (GstElement * rtpbin, guint session, guint ssrc,
+ GstRTSPStream * stream)
+ {
+ /* TODO: What to do here other than this? */
+ GST_DEBUG ("Stream %p: Got EOS", stream);
+ gst_pad_send_event (stream->priv->sinkpad, gst_event_new_eos ());
+ }
+
+ typedef struct _ProbeData ProbeData;
+
+ struct _ProbeData
+ {
+ GstRTSPStream *stream;
+ /* existing sink, already linked to tee */
+ GstElement *sink1;
+ /* new sink, about to be linked */
+ GstElement *sink2;
+ /* new queue element, that will be linked to tee and sink1 */
+ GstElement **queue1;
+ /* new queue element, that will be linked to tee and sink2 */
+ GstElement **queue2;
+ GstPad *sink_pad;
+ GstPad *tee_pad;
+ guint index;
+ };
+
+ static void
+ free_cb_data (gpointer user_data)
+ {
+ ProbeData *data = user_data;
+
+ gst_object_unref (data->stream);
+ gst_object_unref (data->sink1);
+ gst_object_unref (data->sink2);
+ gst_object_unref (data->sink_pad);
+ gst_object_unref (data->tee_pad);
+ g_free (data);
+ }
+
+
+ static void
+ create_and_plug_queue_to_unlinked_stream (GstRTSPStream * stream,
+ GstElement * tee, GstElement * sink, GstElement ** queue)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstPad *tee_pad;
+ GstPad *queue_pad;
+ GstPad *sink_pad;
+
+ /* create queue for the new stream */
+ *queue = gst_element_factory_make ("queue", NULL);
+ g_object_set (*queue, "max-size-buffers", 1, "max-size-bytes", 0,
+ "max-size-time", G_GINT64_CONSTANT (0), NULL);
+ gst_bin_add (priv->joined_bin, *queue);
+
+ /* link tee to queue */
+ tee_pad = gst_element_request_pad_simple (tee, "src_%u");
+ queue_pad = gst_element_get_static_pad (*queue, "sink");
+ gst_pad_link (tee_pad, queue_pad);
+ gst_object_unref (queue_pad);
+ gst_object_unref (tee_pad);
+
+ /* link queue to sink */
+ queue_pad = gst_element_get_static_pad (*queue, "src");
+ sink_pad = gst_element_get_static_pad (sink, "sink");
+ gst_pad_link (queue_pad, sink_pad);
+ gst_object_unref (queue_pad);
+ gst_object_unref (sink_pad);
+
+ gst_element_sync_state_with_parent (sink);
+ gst_element_sync_state_with_parent (*queue);
+ }
+
+ static GstPadProbeReturn
+ create_and_plug_queue_to_linked_stream_probe_cb (GstPad * inpad,
+ GstPadProbeInfo * info, gpointer user_data)
+ {
+ GstRTSPStreamPrivate *priv;
+ ProbeData *data = user_data;
+ GstRTSPStream *stream;
+ GstElement **queue1;
+ GstElement **queue2;
+ GstPad *sink_pad;
+ GstPad *tee_pad;
+ GstPad *queue_pad;
+ guint index;
+
+ stream = data->stream;
+ priv = stream->priv;
+ queue1 = data->queue1;
+ queue2 = data->queue2;
+ sink_pad = data->sink_pad;
+ tee_pad = data->tee_pad;
+ index = data->index;
+
+ /* unlink tee and the existing sink:
+ * .-----. .---------.
+ * | tee | | sink1 |
+ * sink src->sink |
+ * '-----' '---------'
+ */
+ g_assert (gst_pad_unlink (tee_pad, sink_pad));
+
+ /* add queue to the already existing stream */
+ *queue1 = gst_element_factory_make ("queue", NULL);
+ g_object_set (*queue1, "max-size-buffers", 1, "max-size-bytes", 0,
+ "max-size-time", G_GINT64_CONSTANT (0), NULL);
+ gst_bin_add (priv->joined_bin, *queue1);
+
+ /* link tee, queue and sink:
+ * .-----. .---------. .---------.
+ * | tee | | queue1 | | sink1 |
+ * sink src->sink src->sink |
+ * '-----' '---------' '---------'
+ */
+ queue_pad = gst_element_get_static_pad (*queue1, "sink");
+ gst_pad_link (tee_pad, queue_pad);
+ gst_object_unref (queue_pad);
+ queue_pad = gst_element_get_static_pad (*queue1, "src");
+ gst_pad_link (queue_pad, sink_pad);
+ gst_object_unref (queue_pad);
+
+ gst_element_sync_state_with_parent (*queue1);
+
+ /* create queue and link it to tee and the new sink */
+ create_and_plug_queue_to_unlinked_stream (stream,
+ priv->tee[index], data->sink2, queue2);
+
+ /* the final stream:
+ *
+ * .-----. .---------. .---------.
+ * | tee | | queue1 | | sink1 |
+ * sink src->sink src->sink |
+ * | | '---------' '---------'
+ * | | .---------. .---------.
+ * | | | queue2 | | sink2 |
+ * | src->sink src->sink |
+ * '-----' '---------' '---------'
+ */
+
+ return GST_PAD_PROBE_REMOVE;
+ }
+
+ static void
+ create_and_plug_queue_to_linked_stream (GstRTSPStream * stream,
+ GstElement * sink1, GstElement * sink2, guint index, GstElement ** queue1,
+ GstElement ** queue2)
+ {
+ ProbeData *data;
+
+ data = g_new0 (ProbeData, 1);
+ data->stream = gst_object_ref (stream);
+ data->sink1 = gst_object_ref (sink1);
+ data->sink2 = gst_object_ref (sink2);
+ data->queue1 = queue1;
+ data->queue2 = queue2;
+ data->index = index;
+
+ data->sink_pad = gst_element_get_static_pad (sink1, "sink");
+ g_assert (data->sink_pad);
+ data->tee_pad = gst_pad_get_peer (data->sink_pad);
+ g_assert (data->tee_pad);
+
+ gst_pad_add_probe (data->tee_pad, GST_PAD_PROBE_TYPE_IDLE,
+ create_and_plug_queue_to_linked_stream_probe_cb, data, free_cb_data);
+ }
+
+ static void
+ plug_udp_sink (GstRTSPStream * stream, GstElement * sink_to_plug,
+ GstElement ** queue_to_plug, guint index, gboolean is_mcast)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GstElement *existing_sink;
+
+ if (is_mcast)
+ existing_sink = priv->udpsink[index];
+ else
+ existing_sink = priv->mcast_udpsink[index];
+
+ GST_DEBUG_OBJECT (stream, "plug %s sink", is_mcast ? "mcast" : "udp");
+
+ /* add sink to the bin */
+ gst_bin_add (priv->joined_bin, sink_to_plug);
+
+ if (priv->appsink[index] && existing_sink) {
+
+ /* queues are already added for the existing stream, add one for
+ the newly added udp stream */
+ create_and_plug_queue_to_unlinked_stream (stream, priv->tee[index],
+ sink_to_plug, queue_to_plug);
+
+ } else if (priv->appsink[index] || existing_sink) {
+ GstElement **queue;
+ GstElement *element;
+
+ /* add queue to the already existing stream plus the newly created udp
+ stream */
+ if (priv->appsink[index]) {
+ element = priv->appsink[index];
+ queue = &priv->appqueue[index];
+ } else {
+ element = existing_sink;
+ if (is_mcast)
+ queue = &priv->udpqueue[index];
+ else
+ queue = &priv->mcast_udpqueue[index];
+ }
+
+ create_and_plug_queue_to_linked_stream (stream, element, sink_to_plug,
+ index, queue, queue_to_plug);
+
+ } else {
+ GstPad *tee_pad;
+ GstPad *sink_pad;
+
+ GST_DEBUG_OBJECT (stream, "creating first stream");
+
+ /* no need to add queues */
+ tee_pad = gst_element_request_pad_simple (priv->tee[index], "src_%u");
+ sink_pad = gst_element_get_static_pad (sink_to_plug, "sink");
+ gst_pad_link (tee_pad, sink_pad);
+ gst_object_unref (tee_pad);
+ gst_object_unref (sink_pad);
+ }
+
+ gst_element_sync_state_with_parent (sink_to_plug);
+ }
+
+ static void
+ plug_tcp_sink (GstRTSPStream * stream, guint index)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ GST_DEBUG_OBJECT (stream, "plug tcp sink");
+
+ /* add sink to the bin */
+ gst_bin_add (priv->joined_bin, priv->appsink[index]);
+
+ if (priv->mcast_udpsink[index] && priv->udpsink[index]) {
+
+ /* queues are already added for the existing stream, add one for
+ the newly added tcp stream */
+ create_and_plug_queue_to_unlinked_stream (stream,
+ priv->tee[index], priv->appsink[index], &priv->appqueue[index]);
+
+ } else if (priv->mcast_udpsink[index] || priv->udpsink[index]) {
+ GstElement **queue;
+ GstElement *element;
+
+ /* add queue to the already existing stream plus the newly created tcp
+ stream */
+ if (priv->mcast_udpsink[index]) {
+ element = priv->mcast_udpsink[index];
+ queue = &priv->mcast_udpqueue[index];
+ } else {
+ element = priv->udpsink[index];
+ queue = &priv->udpqueue[index];
+ }
+
+ create_and_plug_queue_to_linked_stream (stream, element,
+ priv->appsink[index], index, queue, &priv->appqueue[index]);
+
+ } else {
+ GstPad *tee_pad;
+ GstPad *sink_pad;
+
+ /* no need to add queues */
+ tee_pad = gst_element_request_pad_simple (priv->tee[index], "src_%u");
+ sink_pad = gst_element_get_static_pad (priv->appsink[index], "sink");
+ gst_pad_link (tee_pad, sink_pad);
+ gst_object_unref (tee_pad);
+ gst_object_unref (sink_pad);
+ }
+
+ gst_element_sync_state_with_parent (priv->appsink[index]);
+ }
+
+ static void
+ plug_sink (GstRTSPStream * stream, const GstRTSPTransport * transport,
+ guint index)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean is_tcp, is_udp, is_mcast;
+ priv = stream->priv;
+
+ is_tcp = transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP;
+ is_udp = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP;
+ is_mcast = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST;
+
+ if (is_udp)
+ plug_udp_sink (stream, priv->udpsink[index],
+ &priv->udpqueue[index], index, FALSE);
+
+ else if (is_mcast)
+ plug_udp_sink (stream, priv->mcast_udpsink[index],
+ &priv->mcast_udpqueue[index], index, TRUE);
+
+ else if (is_tcp)
+ plug_tcp_sink (stream, index);
+ }
+
+ /* must be called with lock */
+ static gboolean
+ create_sender_part (GstRTSPStream * stream, const GstRTSPTransport * transport)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstPad *pad;
+ GstBin *bin;
+ gboolean is_tcp, is_udp, is_mcast;
+ gint mcast_ttl = 0;
+ gint i;
+
+ GST_DEBUG_OBJECT (stream, "create sender part");
+ priv = stream->priv;
+ bin = priv->joined_bin;
+
+ is_tcp = transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP;
+ is_udp = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP;
+ is_mcast = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST;
+
+ if (is_mcast)
+ mcast_ttl = transport->ttl;
+
+ GST_DEBUG_OBJECT (stream, "tcp: %d, udp: %d, mcast: %d (ttl: %d)", is_tcp,
+ is_udp, is_mcast, mcast_ttl);
+
+ if (is_udp && !priv->server_addr_v4 && !priv->server_addr_v6) {
+ GST_WARNING_OBJECT (stream, "no sockets assigned for UDP");
+ return FALSE;
+ }
+
+ if (is_mcast && !priv->mcast_addr_v4 && !priv->mcast_addr_v6) {
+ GST_WARNING_OBJECT (stream, "no sockets assigned for UDP multicast");
+ return FALSE;
+ }
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->payloader),
+ "onvif-no-rate-control"))
+ g_object_set (priv->payloader, "onvif-no-rate-control",
+ !priv->do_rate_control, NULL);
+
+ for (i = 0; i < (priv->enable_rtcp ? 2 : 1); i++) {
+ gboolean link_tee = FALSE;
+ /* For the sender we create this bit of pipeline for both
+ * RTP and RTCP (when enabled).
+ * Initially there will be only one active transport for
+ * the stream, so the pipeline will look like this:
+ *
+ * .--------. .-----. .---------.
+ * | rtpbin | | tee | | sink |
+ * | send->sink src->sink |
+ * '--------' '-----' '---------'
+ *
+ * For each new transport, the already existing branch will
+ * be reconfigured by adding a queue element:
+ *
+ * .--------. .-----. .---------. .---------.
+ * | rtpbin | | tee | | queue | | udpsink |
+ * | send->sink src->sink src->sink |
+ * '--------' | | '---------' '---------'
+ * | | .---------. .---------.
+ * | | | queue | | udpsink |
+ * | src->sink src->sink |
+ * | | '---------' '---------'
+ * | | .---------. .---------.
+ * | | | queue | | appsink |
+ * | src->sink src->sink |
+ * '-----' '---------' '---------'
+ */
+
+ /* Only link the RTP send src if we're going to send RTP, link
+ * the RTCP send src always */
+ if (!priv->srcpad && i == 0)
+ continue;
+
+ if (!priv->tee[i]) {
+ /* make tee for RTP/RTCP */
+ priv->tee[i] = gst_element_factory_make ("tee", NULL);
+ gst_bin_add (bin, priv->tee[i]);
+ link_tee = TRUE;
+ }
+
+ if (is_udp && !priv->udpsink[i]) {
+ /* we create only one pair of udpsinks for IPv4 and IPv6 */
+ create_and_configure_udpsink (stream, &priv->udpsink[i],
+ priv->socket_v4[i], priv->socket_v6[i], FALSE, (i == 0), mcast_ttl);
+ plug_sink (stream, transport, i);
+ } else if (is_mcast && !priv->mcast_udpsink[i]) {
+ /* we create only one pair of mcast-udpsinks for IPv4 and IPv6 */
+ create_and_configure_udpsink (stream, &priv->mcast_udpsink[i],
+ priv->mcast_socket_v4[i], priv->mcast_socket_v6[i], TRUE, (i == 0),
+ mcast_ttl);
+ plug_sink (stream, transport, i);
+ } else if (is_tcp && !priv->appsink[i]) {
+ /* make appsink */
+ priv->appsink[i] = gst_element_factory_make ("appsink", NULL);
+ g_object_set (priv->appsink[i], "emit-signals", FALSE, "buffer-list",
+ TRUE, "max-buffers", 1, NULL);
+
+ if (i == 0)
+ g_object_set (priv->appsink[i], "sync", priv->do_rate_control, NULL);
+
+ /* we need to set sync and preroll to FALSE for the sink to avoid
+ * deadlock. This is only needed for sink sending RTCP data. */
+ if (i == 1)
+ g_object_set (priv->appsink[i], "async", FALSE, "sync", FALSE, NULL);
+
+ gst_app_sink_set_callbacks (GST_APP_SINK_CAST (priv->appsink[i]),
+ &sink_cb, stream, NULL);
+ plug_sink (stream, transport, i);
+ }
+
+ if (link_tee) {
+ /* and link to rtpbin send pad */
+ gst_element_sync_state_with_parent (priv->tee[i]);
+ pad = gst_element_get_static_pad (priv->tee[i], "sink");
+ gst_pad_link (priv->send_src[i], pad);
+ gst_object_unref (pad);
+ }
+ }
+
+ return TRUE;
+ }
+
+ /* must be called with lock */
+ static void
+ plug_src (GstRTSPStream * stream, GstBin * bin, GstElement * src,
+ GstElement * funnel)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstPad *pad, *selpad;
+ gulong id = 0;
+
+ priv = stream->priv;
+
+ /* add src */
+ gst_bin_add (bin, src);
+
+ pad = gst_element_get_static_pad (src, "src");
+ if (priv->srcpad) {
+ /* block pad so src can't push data while it's not yet linked */
+ id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK |
+ GST_PAD_PROBE_TYPE_BUFFER, NULL, NULL, NULL);
+ /* we set and keep these to playing so that they don't cause NO_PREROLL return
+ * values. This is only relevant for PLAY pipelines */
+ gst_element_set_state (src, GST_STATE_PLAYING);
+ gst_element_set_locked_state (src, TRUE);
+ }
+
+ /* and link to the funnel */
+ selpad = gst_element_request_pad_simple (funnel, "sink_%u");
+ gst_pad_link (pad, selpad);
+ if (id != 0)
+ gst_pad_remove_probe (pad, id);
+ gst_object_unref (pad);
+ gst_object_unref (selpad);
+ }
+
+ /* must be called with lock */
+ static gboolean
+ create_receiver_part (GstRTSPStream * stream, const GstRTSPTransport *
+ transport)
+ {
+ gboolean ret = FALSE;
+ GstRTSPStreamPrivate *priv;
+ GstPad *pad;
+ GstBin *bin;
+ gboolean tcp;
+ gboolean udp;
+ gboolean mcast;
+ gboolean secure;
+ gint i;
+ GstCaps *rtp_caps;
+ GstCaps *rtcp_caps;
+
+ GST_DEBUG_OBJECT (stream, "create receiver part");
+ priv = stream->priv;
+ bin = priv->joined_bin;
+
+ tcp = transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP;
+ udp = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP;
+ mcast = transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST;
+ secure = (priv->profiles & GST_RTSP_PROFILE_SAVP)
+ || (priv->profiles & GST_RTSP_PROFILE_SAVPF);
+
+ if (secure) {
+ rtp_caps = gst_caps_new_empty_simple ("application/x-srtp");
+ rtcp_caps = gst_caps_new_empty_simple ("application/x-srtcp");
+ } else {
+ rtp_caps = gst_caps_new_empty_simple ("application/x-rtp");
+ rtcp_caps = gst_caps_new_empty_simple ("application/x-rtcp");
+ }
+
+ GST_DEBUG_OBJECT (stream,
+ "RTP caps: %" GST_PTR_FORMAT " RTCP caps: %" GST_PTR_FORMAT, rtp_caps,
+ rtcp_caps);
+
+ for (i = 0; i < (priv->enable_rtcp ? 2 : 1); i++) {
+ /* For the receiver we create this bit of pipeline for both
+ * RTP and RTCP (when enabled). We receive RTP/RTCP on appsrc and udpsrc
+ * and it is all funneled into the rtpbin receive pad.
+ *
+ *
+ * .--------. .--------. .--------.
+ * | udpsrc | | funnel | | rtpbin |
+ * | RTP src->sink src->sink |
+ * '--------' | | | |
+ * .--------. | | | |
+ * | appsrc | | | | |
+ * | RTP src->sink | | |
+ * '--------' '--------' | |
+ * | |
+ * .--------. .--------. | |
+ * | udpsrc | | funnel | | |
+ * | RTCP src->sink src->sink |
+ * '--------' | | '--------'
+ * .--------. | |
+ * | appsrc | | |
+ * | RTCP src->sink |
+ * '--------' '--------'
+ */
+
+ if (!priv->sinkpad && i == 0) {
+ /* Only connect recv RTP sink if we expect to receive RTP. Connect recv
+ * RTCP sink always */
+ continue;
+ }
+
+ /* make funnel for the RTP/RTCP receivers */
+ if (!priv->funnel[i]) {
+ priv->funnel[i] = gst_element_factory_make ("funnel", NULL);
+ gst_bin_add (bin, priv->funnel[i]);
+
+ pad = gst_element_get_static_pad (priv->funnel[i], "src");
+ gst_pad_link (pad, priv->recv_sink[i]);
+ gst_object_unref (pad);
+ }
+
+ if (udp && !priv->udpsrc_v4[i] && priv->server_addr_v4) {
+ GST_DEBUG_OBJECT (stream, "udp IPv4, create and configure udpsources");
+ if (!create_and_configure_udpsource (&priv->udpsrc_v4[i],
+ priv->socket_v4[i]))
+ goto done;
+
+ if (i == 0) {
+ g_object_set (priv->udpsrc_v4[i], "caps", rtp_caps, NULL);
+ } else {
+ g_object_set (priv->udpsrc_v4[i], "caps", rtcp_caps, NULL);
+
+ /* block early rtcp packets, pipeline not ready */
+ g_assert (priv->block_early_rtcp_pad == NULL);
+ priv->block_early_rtcp_pad = gst_element_get_static_pad
+ (priv->udpsrc_v4[i], "src");
+ priv->block_early_rtcp_probe = gst_pad_add_probe
+ (priv->block_early_rtcp_pad,
+ GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER, NULL, NULL,
+ NULL);
+ }
+
+ plug_src (stream, bin, priv->udpsrc_v4[i], priv->funnel[i]);
+ }
+
+ if (udp && !priv->udpsrc_v6[i] && priv->server_addr_v6) {
+ GST_DEBUG_OBJECT (stream, "udp IPv6, create and configure udpsources");
+ if (!create_and_configure_udpsource (&priv->udpsrc_v6[i],
+ priv->socket_v6[i]))
+ goto done;
+
+ if (i == 0) {
+ g_object_set (priv->udpsrc_v6[i], "caps", rtp_caps, NULL);
+ } else {
+ g_object_set (priv->udpsrc_v6[i], "caps", rtcp_caps, NULL);
+
+ /* block early rtcp packets, pipeline not ready */
+ g_assert (priv->block_early_rtcp_pad_ipv6 == NULL);
+ priv->block_early_rtcp_pad_ipv6 = gst_element_get_static_pad
+ (priv->udpsrc_v6[i], "src");
+ priv->block_early_rtcp_probe_ipv6 = gst_pad_add_probe
+ (priv->block_early_rtcp_pad_ipv6,
+ GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER, NULL, NULL,
+ NULL);
+ }
+
+ plug_src (stream, bin, priv->udpsrc_v6[i], priv->funnel[i]);
+ }
+
+ if (mcast && !priv->mcast_udpsrc_v4[i] && priv->mcast_addr_v4) {
+ GST_DEBUG_OBJECT (stream, "mcast IPv4, create and configure udpsources");
+ if (!create_and_configure_udpsource (&priv->mcast_udpsrc_v4[i],
+ priv->mcast_socket_v4[i]))
+ goto done;
+
+ if (i == 0) {
+ g_object_set (priv->mcast_udpsrc_v4[i], "caps", rtp_caps, NULL);
+ } else {
+ g_object_set (priv->mcast_udpsrc_v4[i], "caps", rtcp_caps, NULL);
+ }
+
+ plug_src (stream, bin, priv->mcast_udpsrc_v4[i], priv->funnel[i]);
+ }
+
+ if (mcast && !priv->mcast_udpsrc_v6[i] && priv->mcast_addr_v6) {
+ GST_DEBUG_OBJECT (stream, "mcast IPv6, create and configure udpsources");
+ if (!create_and_configure_udpsource (&priv->mcast_udpsrc_v6[i],
+ priv->mcast_socket_v6[i]))
+ goto done;
+
+ if (i == 0) {
+ g_object_set (priv->mcast_udpsrc_v6[i], "caps", rtp_caps, NULL);
+ } else {
+ g_object_set (priv->mcast_udpsrc_v6[i], "caps", rtcp_caps, NULL);
+ }
+
+ plug_src (stream, bin, priv->mcast_udpsrc_v6[i], priv->funnel[i]);
+ }
+
+ if (tcp && !priv->appsrc[i]) {
+ /* make and add appsrc */
+ priv->appsrc[i] = gst_element_factory_make ("appsrc", NULL);
+ priv->appsrc_base_time[i] = -1;
+ g_object_set (priv->appsrc[i], "format", GST_FORMAT_TIME, "is-live",
+ TRUE, NULL);
+ plug_src (stream, bin, priv->appsrc[i], priv->funnel[i]);
+ }
+
+ gst_element_sync_state_with_parent (priv->funnel[i]);
+ }
+
+ ret = TRUE;
+
+ done:
+ gst_caps_unref (rtp_caps);
+ gst_caps_unref (rtcp_caps);
+ return ret;
+ }
+
+ gboolean
+ gst_rtsp_stream_is_tcp_receiver (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean ret = FALSE;
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ ret = (priv->sinkpad != NULL && priv->appsrc[0] != NULL);
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ static gboolean
+ check_mcast_client_addr (GstRTSPStream * stream, const GstRTSPTransport * tr)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GList *walk;
+
+ if (priv->mcast_clients == NULL)
+ goto no_addr;
+
+ if (tr == NULL)
+ goto no_transport;
+
+ if (tr->destination == NULL)
+ goto no_destination;
+
+ for (walk = priv->mcast_clients; walk; walk = g_list_next (walk)) {
+ UdpClientAddrInfo *cli = walk->data;
+
+ if ((g_strcmp0 (cli->address, tr->destination) == 0) &&
+ (cli->rtp_port == tr->port.min))
+ return TRUE;
+ }
+
+ return FALSE;
+
+ no_addr:
+ {
+ GST_WARNING_OBJECT (stream, "Adding mcast transport, but no mcast address "
+ "has been reserved");
+ return FALSE;
+ }
+ no_transport:
+ {
+ GST_WARNING_OBJECT (stream, "Adding mcast transport, but no transport "
+ "has been provided");
+ return FALSE;
+ }
+ no_destination:
+ {
+ GST_WARNING_OBJECT (stream, "Adding mcast transport, but it doesn't match "
+ "the reserved address");
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_join_bin:
+ * @stream: a #GstRTSPStream
+ * @bin: (transfer none): a #GstBin to join
+ * @rtpbin: (transfer none): a rtpbin element in @bin
+ * @state: the target state of the new elements
+ *
+ * Join the #GstBin @bin that contains the element @rtpbin.
+ *
+ * @stream will link to @rtpbin, which must be inside @bin. The elements
+ * added to @bin will be set to the state given in @state.
+ *
+ * Returns: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_stream_join_bin (GstRTSPStream * stream, GstBin * bin,
+ GstElement * rtpbin, GstState state)
+ {
+ GstRTSPStreamPrivate *priv;
+ guint idx;
+ gchar *name;
+ GstPadLinkReturn ret;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (rtpbin), FALSE);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if (priv->joined_bin != NULL)
+ goto was_joined;
+
+ /* create a session with the same index as the stream */
+ idx = priv->idx;
+
+ GST_INFO ("stream %p joining bin as session %u", stream, idx);
+
+ if (priv->profiles & GST_RTSP_PROFILE_SAVP
+ || priv->profiles & GST_RTSP_PROFILE_SAVPF) {
+ /* For SRTP */
+ g_signal_connect (rtpbin, "request-rtp-encoder",
+ (GCallback) request_rtp_encoder, stream);
+ g_signal_connect (rtpbin, "request-rtcp-encoder",
+ (GCallback) request_rtcp_encoder, stream);
+ g_signal_connect (rtpbin, "request-rtp-decoder",
+ (GCallback) request_rtp_rtcp_decoder, stream);
+ g_signal_connect (rtpbin, "request-rtcp-decoder",
+ (GCallback) request_rtp_rtcp_decoder, stream);
+ }
+
+ if (priv->sinkpad) {
+ g_signal_connect (rtpbin, "request-pt-map",
+ (GCallback) request_pt_map, stream);
+ }
+
+ /* get pads from the RTP session element for sending and receiving
+ * RTP/RTCP*/
+ if (priv->srcpad) {
+ /* get a pad for sending RTP */
+ name = g_strdup_printf ("send_rtp_sink_%u", idx);
+ priv->send_rtp_sink = gst_element_request_pad_simple (rtpbin, name);
+ g_free (name);
+
+ /* link the RTP pad to the session manager, it should not really fail unless
+ * this is not really an RTP pad */
+ ret = gst_pad_link (priv->srcpad, priv->send_rtp_sink);
+ if (ret != GST_PAD_LINK_OK)
+ goto link_failed;
+
+ name = g_strdup_printf ("send_rtp_src_%u", idx);
+ priv->send_src[0] = gst_element_get_static_pad (rtpbin, name);
+ g_free (name);
+ } else {
+ /* RECORD case: need to connect our sinkpad from here */
+ g_signal_connect (rtpbin, "pad-added", (GCallback) pad_added, stream);
+ /* EOS */
+ g_signal_connect (rtpbin, "on-npt-stop", (GCallback) on_npt_stop, stream);
+
+ name = g_strdup_printf ("recv_rtp_sink_%u", idx);
+ priv->recv_sink[0] = gst_element_request_pad_simple (rtpbin, name);
+ g_free (name);
+ }
+
+ if (priv->enable_rtcp) {
+ name = g_strdup_printf ("send_rtcp_src_%u", idx);
+ priv->send_src[1] = gst_element_request_pad_simple (rtpbin, name);
+ g_free (name);
+
+ name = g_strdup_printf ("recv_rtcp_sink_%u", idx);
+ priv->recv_sink[1] = gst_element_request_pad_simple (rtpbin, name);
+ g_free (name);
+ }
+
+ /* get the session */
+ g_signal_emit_by_name (rtpbin, "get-internal-session", idx, &priv->session);
+
+ g_signal_connect (priv->session, "on-new-ssrc", (GCallback) on_new_ssrc,
+ stream);
+ g_signal_connect (priv->session, "on-ssrc-sdes", (GCallback) on_ssrc_sdes,
+ stream);
+ g_signal_connect (priv->session, "on-ssrc-active",
+ (GCallback) on_ssrc_active, stream);
+ g_signal_connect (priv->session, "on-bye-ssrc", (GCallback) on_bye_ssrc,
+ stream);
+ g_signal_connect (priv->session, "on-bye-timeout",
+ (GCallback) on_bye_timeout, stream);
+ g_signal_connect (priv->session, "on-timeout", (GCallback) on_timeout,
+ stream);
+
+ /* signal for sender ssrc */
+ g_signal_connect (priv->session, "on-new-sender-ssrc",
+ (GCallback) on_new_sender_ssrc, stream);
+ g_signal_connect (priv->session, "on-sender-ssrc-active",
+ (GCallback) on_sender_ssrc_active, stream);
+
+ g_object_set (priv->session, "disable-sr-timestamp", !priv->do_rate_control,
+ NULL);
+
+ if (priv->srcpad) {
+ /* be notified of caps changes */
+ priv->caps_sig = g_signal_connect (priv->send_src[0], "notify::caps",
+ (GCallback) caps_notify, stream);
+ priv->caps = gst_pad_get_current_caps (priv->send_src[0]);
+ }
+
+ priv->joined_bin = bin;
+ GST_DEBUG_OBJECT (stream, "successfully joined bin");
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ was_joined:
+ {
+ g_mutex_unlock (&priv->lock);
+ return TRUE;
+ }
+ link_failed:
+ {
+ GST_WARNING ("failed to link stream %u", idx);
+ gst_object_unref (priv->send_rtp_sink);
+ priv->send_rtp_sink = NULL;
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ static void
+ clear_element (GstBin * bin, GstElement ** elementptr)
+ {
+ if (*elementptr) {
+ gst_element_set_locked_state (*elementptr, FALSE);
+ gst_element_set_state (*elementptr, GST_STATE_NULL);
+ if (GST_ELEMENT_PARENT (*elementptr))
+ gst_bin_remove (bin, *elementptr);
+ else
+ gst_object_unref (*elementptr);
+ *elementptr = NULL;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_leave_bin:
+ * @stream: a #GstRTSPStream
+ * @bin: (transfer none): a #GstBin
+ * @rtpbin: (transfer none): a rtpbin #GstElement
+ *
+ * Remove the elements of @stream from @bin.
+ *
+ * Return: %TRUE on success.
+ */
+ gboolean
+ gst_rtsp_stream_leave_bin (GstRTSPStream * stream, GstBin * bin,
+ GstElement * rtpbin)
+ {
+ GstRTSPStreamPrivate *priv;
+ gint i;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
+ g_return_val_if_fail (GST_IS_ELEMENT (rtpbin), FALSE);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->send_lock);
+ priv->continue_sending = FALSE;
+ priv->send_cookie++;
+ g_cond_signal (&priv->send_cond);
+ g_mutex_unlock (&priv->send_lock);
+
+ if (priv->send_thread) {
+ g_thread_join (priv->send_thread);
+ }
+
+ g_mutex_lock (&priv->lock);
+ if (priv->joined_bin == NULL)
+ goto was_not_joined;
+ if (priv->joined_bin != bin)
+ goto wrong_bin;
+
+ priv->joined_bin = NULL;
+
+ /* all transports must be removed by now */
+ if (priv->transports != NULL)
+ goto transports_not_removed;
+
+ if (priv->send_pool) {
+ GThreadPool *slask;
+
+ slask = priv->send_pool;
+ priv->send_pool = NULL;
+ g_mutex_unlock (&priv->lock);
+ g_thread_pool_free (slask, TRUE, TRUE);
+ g_mutex_lock (&priv->lock);
+ }
+
+ clear_tr_cache (priv);
+
+ GST_INFO ("stream %p leaving bin", stream);
+
+ if (priv->srcpad) {
+ gst_pad_unlink (priv->srcpad, priv->send_rtp_sink);
+
+ g_signal_handler_disconnect (priv->send_src[0], priv->caps_sig);
+ gst_element_release_request_pad (rtpbin, priv->send_rtp_sink);
+ gst_object_unref (priv->send_rtp_sink);
+ priv->send_rtp_sink = NULL;
+ } else if (priv->recv_rtp_src) {
+ gst_pad_unlink (priv->recv_rtp_src, priv->sinkpad);
+ gst_object_unref (priv->recv_rtp_src);
+ priv->recv_rtp_src = NULL;
+ }
+
+ for (i = 0; i < (priv->enable_rtcp ? 2 : 1); i++) {
+ clear_element (bin, &priv->udpsrc_v4[i]);
+ clear_element (bin, &priv->udpsrc_v6[i]);
+ clear_element (bin, &priv->udpqueue[i]);
+ clear_element (bin, &priv->udpsink[i]);
+
+ clear_element (bin, &priv->mcast_udpsrc_v4[i]);
+ clear_element (bin, &priv->mcast_udpsrc_v6[i]);
+ clear_element (bin, &priv->mcast_udpqueue[i]);
+ clear_element (bin, &priv->mcast_udpsink[i]);
+
+ clear_element (bin, &priv->appsrc[i]);
+ clear_element (bin, &priv->appqueue[i]);
+ clear_element (bin, &priv->appsink[i]);
+
+ clear_element (bin, &priv->tee[i]);
+ clear_element (bin, &priv->funnel[i]);
+
+ if (priv->sinkpad || i == 1) {
+ gst_element_release_request_pad (rtpbin, priv->recv_sink[i]);
+ gst_object_unref (priv->recv_sink[i]);
+ priv->recv_sink[i] = NULL;
+ }
+ }
+
+ if (priv->srcpad) {
+ gst_object_unref (priv->send_src[0]);
+ priv->send_src[0] = NULL;
+ }
+
+ if (priv->enable_rtcp) {
+ gst_element_release_request_pad (rtpbin, priv->send_src[1]);
+ gst_object_unref (priv->send_src[1]);
+ priv->send_src[1] = NULL;
+ }
+
+ g_object_unref (priv->session);
+ priv->session = NULL;
+ if (priv->caps)
+ gst_caps_unref (priv->caps);
+ priv->caps = NULL;
+
+ if (priv->srtpenc)
+ gst_object_unref (priv->srtpenc);
+ if (priv->srtpdec)
+ gst_object_unref (priv->srtpdec);
+
+ if (priv->mcast_addr_v4)
+ gst_rtsp_address_free (priv->mcast_addr_v4);
+ priv->mcast_addr_v4 = NULL;
+ if (priv->mcast_addr_v6)
+ gst_rtsp_address_free (priv->mcast_addr_v6);
+ priv->mcast_addr_v6 = NULL;
+ if (priv->server_addr_v4)
+ gst_rtsp_address_free (priv->server_addr_v4);
+ priv->server_addr_v4 = NULL;
+ if (priv->server_addr_v6)
+ gst_rtsp_address_free (priv->server_addr_v6);
+ priv->server_addr_v6 = NULL;
+
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+
+ was_not_joined:
+ {
+ g_mutex_unlock (&priv->lock);
+ return TRUE;
+ }
+ transports_not_removed:
+ {
+ GST_ERROR_OBJECT (stream, "can't leave bin (transports not removed)");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ wrong_bin:
+ {
+ GST_ERROR_OBJECT (stream, "leaving the wrong bin");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_get_joined_bin:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the previous joined bin with gst_rtsp_stream_join_bin() or NULL.
+ *
+ * Return: (transfer full) (nullable): the joined bin or NULL.
+ */
+ GstBin *
+ gst_rtsp_stream_get_joined_bin (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstBin *bin = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ bin = priv->joined_bin ? gst_object_ref (priv->joined_bin) : NULL;
+ g_mutex_unlock (&priv->lock);
+
+ return bin;
+ }
+
+ /**
+ * gst_rtsp_stream_get_rtpinfo:
+ * @stream: a #GstRTSPStream
+ * @rtptime: (allow-none) (out caller-allocates): result RTP timestamp
+ * @seq: (allow-none) (out caller-allocates): result RTP seqnum
+ * @clock_rate: (allow-none) (out caller-allocates): the clock rate
+ * @running_time: (out caller-allocates): result running-time
+ *
+ * Retrieve the current rtptime, seq and running-time. This is used to
+ * construct a RTPInfo reply header.
+ *
+ * Returns: %TRUE when rtptime, seq and running-time could be determined.
+ */
+ gboolean
+ gst_rtsp_stream_get_rtpinfo (GstRTSPStream * stream,
+ guint * rtptime, guint * seq, guint * clock_rate,
+ GstClockTime * running_time)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstStructure *stats;
+ GObjectClass *payobjclass;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+
+ payobjclass = G_OBJECT_GET_CLASS (priv->payloader);
+
+ g_mutex_lock (&priv->lock);
+
+ /* First try to extract the information from the last buffer on the sinks.
+ * This will have a more accurate sequence number and timestamp, as between
+ * the payloader and the sink there can be some queues
+ */
+ if (priv->udpsink[0] || priv->mcast_udpsink[0] || priv->appsink[0]) {
+ GstSample *last_sample;
+
+ if (priv->udpsink[0])
+ g_object_get (priv->udpsink[0], "last-sample", &last_sample, NULL);
+ else if (priv->mcast_udpsink[0])
+ g_object_get (priv->mcast_udpsink[0], "last-sample", &last_sample, NULL);
+ else
+ g_object_get (priv->appsink[0], "last-sample", &last_sample, NULL);
+
+ if (last_sample) {
+ GstCaps *caps;
+ GstBuffer *buffer;
+ GstSegment *segment;
+ GstStructure *s;
+ GstRTPBuffer rtp_buffer = GST_RTP_BUFFER_INIT;
+
+ caps = gst_sample_get_caps (last_sample);
+ buffer = gst_sample_get_buffer (last_sample);
+ segment = gst_sample_get_segment (last_sample);
+ s = gst_caps_get_structure (caps, 0);
+
+ if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp_buffer)) {
+ guint ssrc_buf = gst_rtp_buffer_get_ssrc (&rtp_buffer);
+ guint ssrc_stream = 0;
+ if (gst_structure_has_field_typed (s, "ssrc", G_TYPE_UINT) &&
+ gst_structure_get_uint (s, "ssrc", &ssrc_stream) &&
+ ssrc_buf != ssrc_stream) {
+ /* Skip buffers from auxiliary streams. */
+ GST_DEBUG_OBJECT (stream,
+ "not a buffer from the payloader, SSRC: %08x", ssrc_buf);
+
+ gst_rtp_buffer_unmap (&rtp_buffer);
+ gst_sample_unref (last_sample);
+ goto stats;
+ }
+
+ if (seq) {
+ *seq = gst_rtp_buffer_get_seq (&rtp_buffer);
+ }
+
+ if (rtptime) {
+ *rtptime = gst_rtp_buffer_get_timestamp (&rtp_buffer);
+ }
+
+ gst_rtp_buffer_unmap (&rtp_buffer);
+
+ if (running_time) {
+ *running_time =
+ gst_segment_to_running_time (segment, GST_FORMAT_TIME,
+ GST_BUFFER_TIMESTAMP (buffer));
+ }
+
+ if (clock_rate) {
+ gst_structure_get_int (s, "clock-rate", (gint *) clock_rate);
+
+ if (*clock_rate == 0 && running_time)
+ *running_time = GST_CLOCK_TIME_NONE;
+ }
+ gst_sample_unref (last_sample);
+
+ goto done;
+ } else {
+ gst_sample_unref (last_sample);
+ }
+ } else if (priv->blocking) {
+ if (seq) {
+ if (!priv->blocked_buffer)
+ goto stats;
+ *seq = priv->blocked_seqnum;
+ }
+
+ if (rtptime) {
+ if (!priv->blocked_buffer)
+ goto stats;
+ *rtptime = priv->blocked_rtptime;
+ }
+
+ if (running_time) {
+ if (!GST_CLOCK_TIME_IS_VALID (priv->blocked_running_time))
+ goto stats;
+ *running_time = priv->blocked_running_time;
+ }
+
+ if (clock_rate) {
+ *clock_rate = priv->blocked_clock_rate;
+
+ if (*clock_rate == 0 && running_time)
+ *running_time = GST_CLOCK_TIME_NONE;
+ }
+
+ goto done;
+ }
+ }
+
+ stats:
+ if (g_object_class_find_property (payobjclass, "stats")) {
+ g_object_get (priv->payloader, "stats", &stats, NULL);
+ if (stats == NULL)
+ goto no_stats;
+
+ if (seq)
+ gst_structure_get_uint (stats, "seqnum-offset", seq);
+
+ if (rtptime)
+ gst_structure_get_uint (stats, "timestamp", rtptime);
+
+ if (running_time)
+ gst_structure_get_clock_time (stats, "running-time", running_time);
+
+ if (clock_rate) {
+ gst_structure_get_uint (stats, "clock-rate", clock_rate);
+ if (*clock_rate == 0 && running_time)
+ *running_time = GST_CLOCK_TIME_NONE;
+ }
+ gst_structure_free (stats);
+ } else {
+ if (!g_object_class_find_property (payobjclass, "seqnum") ||
+ !g_object_class_find_property (payobjclass, "timestamp"))
+ goto no_stats;
+
+ if (seq)
+ g_object_get (priv->payloader, "seqnum", seq, NULL);
+
+ if (rtptime)
+ g_object_get (priv->payloader, "timestamp", rtptime, NULL);
+
+ if (running_time)
+ *running_time = GST_CLOCK_TIME_NONE;
+ }
+
+ done:
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_stats:
+ {
+ GST_WARNING ("Could not get payloader stats");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_get_rates:
+ * @stream: a #GstRTSPStream
+ * @rate: (optional) (out caller-allocates): the configured rate
+ * @applied_rate: (optional) (out caller-allocates): the configured applied_rate
+ *
+ * Retrieve the current rate and/or applied_rate.
+ *
+ * Returns: %TRUE if rate and/or applied_rate could be determined.
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_stream_get_rates (GstRTSPStream * stream, gdouble * rate,
+ gdouble * applied_rate)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstEvent *event;
+ const GstSegment *segment;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ if (!rate && !applied_rate) {
+ GST_WARNING_OBJECT (stream, "rate and applied_rate are both NULL");
+ return FALSE;
+ }
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ if (!priv->send_rtp_sink)
+ goto no_rtp_sink_pad;
+
+ event = gst_pad_get_sticky_event (priv->send_rtp_sink, GST_EVENT_SEGMENT, 0);
+ if (!event)
+ goto no_sticky_event;
+
+ gst_event_parse_segment (event, &segment);
+ if (rate)
+ *rate = segment->rate;
+ if (applied_rate)
+ *applied_rate = segment->applied_rate;
+
+ gst_event_unref (event);
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+
+ /* ERRORS */
+ no_rtp_sink_pad:
+ {
+ GST_WARNING_OBJECT (stream, "no send_rtp_sink pad yet");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ no_sticky_event:
+ {
+ GST_WARNING_OBJECT (stream, "no segment event on send_rtp_sink pad");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+
+ }
+
+ /**
+ * gst_rtsp_stream_get_caps:
+ * @stream: a #GstRTSPStream
+ *
+ * Retrieve the current caps of @stream.
+ *
+ * Returns: (transfer full) (nullable): the #GstCaps of @stream.
+ * use gst_caps_unref() after usage.
+ */
+ GstCaps *
+ gst_rtsp_stream_get_caps (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstCaps *result;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ if ((result = priv->caps))
+ gst_caps_ref (result);
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_stream_recv_rtp:
+ * @stream: a #GstRTSPStream
+ * @buffer: (transfer full): a #GstBuffer
+ *
+ * Handle an RTP buffer for the stream. This method is usually called when a
+ * message has been received from a client using the TCP transport.
+ *
+ * This function takes ownership of @buffer.
+ *
+ * Returns: a GstFlowReturn.
+ */
+ GstFlowReturn
+ gst_rtsp_stream_recv_rtp (GstRTSPStream * stream, GstBuffer * buffer)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstFlowReturn ret;
+ GstElement *element;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), GST_FLOW_ERROR);
+ priv = stream->priv;
+ g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
+ g_return_val_if_fail (priv->joined_bin != NULL, FALSE);
+
+ g_mutex_lock (&priv->lock);
+ if (priv->appsrc[0])
+ element = gst_object_ref (priv->appsrc[0]);
+ else
+ element = NULL;
+ g_mutex_unlock (&priv->lock);
+
+ if (element) {
+ if (priv->appsrc_base_time[0] == -1) {
+ /* Take current running_time. This timestamp will be put on
+ * the first buffer of each stream because we are a live source and so we
+ * timestamp with the running_time. When we are dealing with TCP, we also
+ * only timestamp the first buffer (using the DISCONT flag) because a server
+ * typically bursts data, for which we don't want to compensate by speeding
+ * up the media. The other timestamps will be interpollated from this one
+ * using the RTP timestamps. */
+ GST_OBJECT_LOCK (element);
+ if (GST_ELEMENT_CLOCK (element)) {
+ GstClockTime now;
+ GstClockTime base_time;
+
+ now = gst_clock_get_time (GST_ELEMENT_CLOCK (element));
+ base_time = GST_ELEMENT_CAST (element)->base_time;
+
+ priv->appsrc_base_time[0] = now - base_time;
+ GST_BUFFER_TIMESTAMP (buffer) = priv->appsrc_base_time[0];
+ GST_DEBUG ("stream %p: first buffer at time %" GST_TIME_FORMAT
+ ", base %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (now),
+ GST_TIME_ARGS (base_time));
+ }
+ GST_OBJECT_UNLOCK (element);
+ }
+
+ ret = gst_app_src_push_buffer (GST_APP_SRC_CAST (element), buffer);
+ gst_object_unref (element);
+ } else {
+ ret = GST_FLOW_OK;
+ }
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_recv_rtcp:
+ * @stream: a #GstRTSPStream
+ * @buffer: (transfer full): a #GstBuffer
+ *
+ * Handle an RTCP buffer for the stream. This method is usually called when a
+ * message has been received from a client using the TCP transport.
+ *
+ * This function takes ownership of @buffer.
+ *
+ * Returns: a GstFlowReturn.
+ */
+ GstFlowReturn
+ gst_rtsp_stream_recv_rtcp (GstRTSPStream * stream, GstBuffer * buffer)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstFlowReturn ret;
+ GstElement *element;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), GST_FLOW_ERROR);
+ priv = stream->priv;
+ g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR);
+
+ if (priv->joined_bin == NULL) {
+ gst_buffer_unref (buffer);
+ return GST_FLOW_NOT_LINKED;
+ }
+ g_mutex_lock (&priv->lock);
+ if (priv->appsrc[1])
+ element = gst_object_ref (priv->appsrc[1]);
+ else
+ element = NULL;
+ g_mutex_unlock (&priv->lock);
+
+ if (element) {
+ if (priv->appsrc_base_time[1] == -1) {
+ /* Take current running_time. This timestamp will be put on
+ * the first buffer of each stream because we are a live source and so we
+ * timestamp with the running_time. When we are dealing with TCP, we also
+ * only timestamp the first buffer (using the DISCONT flag) because a server
+ * typically bursts data, for which we don't want to compensate by speeding
+ * up the media. The other timestamps will be interpollated from this one
+ * using the RTP timestamps. */
+ GST_OBJECT_LOCK (element);
+ if (GST_ELEMENT_CLOCK (element)) {
+ GstClockTime now;
+ GstClockTime base_time;
+
+ now = gst_clock_get_time (GST_ELEMENT_CLOCK (element));
+ base_time = GST_ELEMENT_CAST (element)->base_time;
+
+ priv->appsrc_base_time[1] = now - base_time;
+ GST_BUFFER_TIMESTAMP (buffer) = priv->appsrc_base_time[1];
+ GST_DEBUG ("stream %p: first buffer at time %" GST_TIME_FORMAT
+ ", base %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (now),
+ GST_TIME_ARGS (base_time));
+ }
+ GST_OBJECT_UNLOCK (element);
+ }
+
+ ret = gst_app_src_push_buffer (GST_APP_SRC_CAST (element), buffer);
+ gst_object_unref (element);
+ } else {
+ ret = GST_FLOW_OK;
+ gst_buffer_unref (buffer);
+ }
+ return ret;
+ }
+
+ /* must be called with lock */
+ static inline void
+ add_client (GstElement * rtp_sink, GstElement * rtcp_sink, const gchar * host,
+ gint rtp_port, gint rtcp_port)
+ {
+ if (rtp_sink != NULL)
+ g_signal_emit_by_name (rtp_sink, "add", host, rtp_port, NULL);
+ if (rtcp_sink != NULL)
+ g_signal_emit_by_name (rtcp_sink, "add", host, rtcp_port, NULL);
+ }
+
+ /* must be called with lock */
+ static void
+ remove_client (GstElement * rtp_sink, GstElement * rtcp_sink,
+ const gchar * host, gint rtp_port, gint rtcp_port)
+ {
+ if (rtp_sink != NULL)
+ g_signal_emit_by_name (rtp_sink, "remove", host, rtp_port, NULL);
+ if (rtcp_sink != NULL)
+ g_signal_emit_by_name (rtcp_sink, "remove", host, rtcp_port, NULL);
+ }
+
+ /* must be called with lock */
+ static gboolean
+ update_transport (GstRTSPStream * stream, GstRTSPStreamTransport * trans,
+ gboolean add)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ const GstRTSPTransport *tr;
+ gchar *dest;
+ gint min, max;
+ GList *tr_element;
+
+ tr = gst_rtsp_stream_transport_get_transport (trans);
+ dest = tr->destination;
+
+ tr_element = g_list_find (priv->transports, trans);
+
+ if (add && tr_element)
+ return TRUE;
+ else if (!add && !tr_element)
+ return FALSE;
+
+ switch (tr->lower_transport) {
+ case GST_RTSP_LOWER_TRANS_UDP_MCAST:
+ {
+ min = tr->port.min;
+ max = tr->port.max;
+
+ if (add) {
+ GST_INFO ("adding %s:%d-%d", dest, min, max);
+ if (!check_mcast_client_addr (stream, tr))
+ goto mcast_error;
+ add_client (priv->mcast_udpsink[0], priv->mcast_udpsink[1], dest, min,
+ max);
+
+ if (tr->ttl > 0) {
+ GST_INFO ("setting ttl-mc %d", tr->ttl);
+ if (priv->mcast_udpsink[0])
+ g_object_set (G_OBJECT (priv->mcast_udpsink[0]), "ttl-mc", tr->ttl,
+ NULL);
+ if (priv->mcast_udpsink[1])
+ g_object_set (G_OBJECT (priv->mcast_udpsink[1]), "ttl-mc", tr->ttl,
+ NULL);
+ }
+ priv->transports = g_list_prepend (priv->transports, trans);
+ } else {
+ GST_INFO ("removing %s:%d-%d", dest, min, max);
+ if (!remove_mcast_client_addr (stream, dest, min, max))
+ GST_WARNING_OBJECT (stream,
+ "Failed to remove multicast address: %s:%d-%d", dest, min, max);
+ priv->transports = g_list_delete_link (priv->transports, tr_element);
+ remove_client (priv->mcast_udpsink[0], priv->mcast_udpsink[1], dest,
+ min, max);
+ }
+ break;
+ }
+ case GST_RTSP_LOWER_TRANS_UDP:
+ {
+ if (priv->client_side) {
+ /* In client side mode the 'destination' is the RTSP server, so send
+ * to those ports */
+ min = tr->server_port.min;
+ max = tr->server_port.max;
+ } else {
+ min = tr->client_port.min;
+ max = tr->client_port.max;
+ }
+
+ if (add) {
+ GST_INFO ("adding %s:%d-%d", dest, min, max);
+ add_client (priv->udpsink[0], priv->udpsink[1], dest, min, max);
+ priv->transports = g_list_prepend (priv->transports, trans);
+ } else {
+ GST_INFO ("removing %s:%d-%d", dest, min, max);
+ priv->transports = g_list_delete_link (priv->transports, tr_element);
+ remove_client (priv->udpsink[0], priv->udpsink[1], dest, min, max);
+ }
+ priv->transports_cookie++;
+ break;
+ }
+ case GST_RTSP_LOWER_TRANS_TCP:
+ if (add) {
+ GST_INFO ("adding TCP %s", tr->destination);
+ priv->transports = g_list_prepend (priv->transports, trans);
+ priv->n_tcp_transports++;
+ } else {
+ GST_INFO ("removing TCP %s", tr->destination);
+ priv->transports = g_list_delete_link (priv->transports, tr_element);
+
+ gst_rtsp_stream_transport_lock_backlog (trans);
+ gst_rtsp_stream_transport_clear_backlog (trans);
+ gst_rtsp_stream_transport_unlock_backlog (trans);
+
+ priv->n_tcp_transports--;
+ }
+ priv->transports_cookie++;
+ break;
+ default:
+ goto unknown_transport;
+ }
+ return TRUE;
+
+ /* ERRORS */
+ unknown_transport:
+ {
+ GST_INFO ("Unknown transport %d", tr->lower_transport);
+ return FALSE;
+ }
+ mcast_error:
+ {
+ return FALSE;
+ }
+ }
+
+ static void
+ on_message_sent (GstRTSPStreamTransport * trans, gpointer user_data)
+ {
+ GstRTSPStream *stream = GST_RTSP_STREAM (user_data);
+ GstRTSPStreamPrivate *priv = stream->priv;
+
+ GST_DEBUG_OBJECT (stream, "message send complete");
+
+ check_transport_backlog (stream, trans);
+
+ g_mutex_lock (&priv->send_lock);
+ priv->send_cookie++;
+ g_cond_signal (&priv->send_cond);
+ g_mutex_unlock (&priv->send_lock);
+ }
+
+ /**
+ * gst_rtsp_stream_add_transport:
+ * @stream: a #GstRTSPStream
+ * @trans: (transfer none): a #GstRTSPStreamTransport
+ *
+ * Add the transport in @trans to @stream. The media of @stream will
+ * then also be send to the values configured in @trans. Adding the
+ * same transport twice will not add it a second time.
+ *
+ * @stream must be joined to a bin.
+ *
+ * @trans must contain a valid #GstRTSPTransport.
+ *
+ * Returns: %TRUE if @trans was added
+ */
+ gboolean
+ gst_rtsp_stream_add_transport (GstRTSPStream * stream,
+ GstRTSPStreamTransport * trans)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ priv = stream->priv;
+ g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), FALSE);
+ g_return_val_if_fail (priv->joined_bin != NULL, FALSE);
+
+ g_mutex_lock (&priv->lock);
+ res = update_transport (stream, trans, TRUE);
+ if (res)
+ gst_rtsp_stream_transport_set_message_sent_full (trans, on_message_sent,
+ stream, NULL);
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_remove_transport:
+ * @stream: a #GstRTSPStream
+ * @trans: (transfer none): a #GstRTSPStreamTransport
+ *
+ * Remove the transport in @trans from @stream. The media of @stream will
+ * not be sent to the values configured in @trans.
+ *
+ * @stream must be joined to a bin.
+ *
+ * @trans must contain a valid #GstRTSPTransport.
+ *
+ * Returns: %TRUE if @trans was removed
+ */
+ gboolean
+ gst_rtsp_stream_remove_transport (GstRTSPStream * stream,
+ GstRTSPStreamTransport * trans)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ priv = stream->priv;
+ g_return_val_if_fail (GST_IS_RTSP_STREAM_TRANSPORT (trans), FALSE);
+ g_return_val_if_fail (priv->joined_bin != NULL, FALSE);
+
+ g_mutex_lock (&priv->lock);
+ res = update_transport (stream, trans, FALSE);
+ g_mutex_unlock (&priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_update_crypto:
+ * @stream: a #GstRTSPStream
+ * @ssrc: the SSRC
+ * @crypto: (transfer none) (allow-none): a #GstCaps with crypto info
+ *
+ * Update the new crypto information for @ssrc in @stream. If information
+ * for @ssrc did not exist, it will be added. If information
+ * for @ssrc existed, it will be replaced. If @crypto is %NULL, it will
+ * be removed from @stream.
+ *
+ * Returns: %TRUE if @crypto could be updated
+ */
+ gboolean
+ gst_rtsp_stream_update_crypto (GstRTSPStream * stream,
+ guint ssrc, GstCaps * crypto)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ g_return_val_if_fail (crypto == NULL || GST_IS_CAPS (crypto), FALSE);
+
+ priv = stream->priv;
+
+ GST_DEBUG_OBJECT (stream, "update key for %08x", ssrc);
+
+ g_mutex_lock (&priv->lock);
+ if (crypto)
+ g_hash_table_insert (priv->keys, GINT_TO_POINTER (ssrc),
+ gst_caps_ref (crypto));
+ else
+ g_hash_table_remove (priv->keys, GINT_TO_POINTER (ssrc));
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_stream_get_rtp_socket:
+ * @stream: a #GstRTSPStream
+ * @family: the socket family
+ *
+ * Get the RTP socket from @stream for a @family.
+ *
+ * @stream must be joined to a bin.
+ *
+ * Returns: (transfer full) (nullable): the RTP socket or %NULL if no
+ * socket could be allocated for @family. Unref after usage
+ */
+ GSocket *
+ gst_rtsp_stream_get_rtp_socket (GstRTSPStream * stream, GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GSocket *socket;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
+ family == G_SOCKET_FAMILY_IPV6, NULL);
+
+ g_mutex_lock (&priv->lock);
+ if (family == G_SOCKET_FAMILY_IPV6)
+ socket = priv->socket_v6[0];
+ else
+ socket = priv->socket_v4[0];
+
+ if (socket != NULL)
+ socket = g_object_ref (socket);
+ g_mutex_unlock (&priv->lock);
+
+ return socket;
+ }
+
+ /**
+ * gst_rtsp_stream_get_rtcp_socket:
+ * @stream: a #GstRTSPStream
+ * @family: the socket family
+ *
+ * Get the RTCP socket from @stream for a @family.
+ *
+ * @stream must be joined to a bin.
+ *
+ * Returns: (transfer full) (nullable): the RTCP socket or %NULL if no
+ * socket could be allocated for @family. Unref after usage
+ */
+ GSocket *
+ gst_rtsp_stream_get_rtcp_socket (GstRTSPStream * stream, GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GSocket *socket;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
+ family == G_SOCKET_FAMILY_IPV6, NULL);
+
+ g_mutex_lock (&priv->lock);
+ if (family == G_SOCKET_FAMILY_IPV6)
+ socket = priv->socket_v6[1];
+ else
+ socket = priv->socket_v4[1];
+
+ if (socket != NULL)
+ socket = g_object_ref (socket);
+ g_mutex_unlock (&priv->lock);
+
+ return socket;
+ }
+
+ /**
+ * gst_rtsp_stream_get_rtp_multicast_socket:
+ * @stream: a #GstRTSPStream
+ * @family: the socket family
+ *
+ * Get the multicast RTP socket from @stream for a @family.
+ *
+ * Returns: (transfer full) (nullable): the multicast RTP socket or %NULL if no
+ *
+ * socket could be allocated for @family. Unref after usage
+ */
+ GSocket *
+ gst_rtsp_stream_get_rtp_multicast_socket (GstRTSPStream * stream,
+ GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GSocket *socket;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
+ family == G_SOCKET_FAMILY_IPV6, NULL);
+
+ g_mutex_lock (&priv->lock);
+ if (family == G_SOCKET_FAMILY_IPV6)
+ socket = priv->mcast_socket_v6[0];
+ else
+ socket = priv->mcast_socket_v4[0];
+
+ if (socket != NULL)
+ socket = g_object_ref (socket);
+ g_mutex_unlock (&priv->lock);
+
+ return socket;
+ }
+
+ /**
+ * gst_rtsp_stream_get_rtcp_multicast_socket:
+ * @stream: a #GstRTSPStream
+ * @family: the socket family
+ *
+ * Get the multicast RTCP socket from @stream for a @family.
+ *
+ * Returns: (transfer full) (nullable): the multicast RTCP socket or %NULL if no
+ * socket could be allocated for @family. Unref after usage
+ *
+ * Since: 1.14
+ */
+ GSocket *
+ gst_rtsp_stream_get_rtcp_multicast_socket (GstRTSPStream * stream,
+ GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv = stream->priv;
+ GSocket *socket;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ g_return_val_if_fail (family == G_SOCKET_FAMILY_IPV4 ||
+ family == G_SOCKET_FAMILY_IPV6, NULL);
+
+ g_mutex_lock (&priv->lock);
+ if (family == G_SOCKET_FAMILY_IPV6)
+ socket = priv->mcast_socket_v6[1];
+ else
+ socket = priv->mcast_socket_v4[1];
+
+ if (socket != NULL)
+ socket = g_object_ref (socket);
+ g_mutex_unlock (&priv->lock);
+
+ return socket;
+ }
+
+ /**
+ * gst_rtsp_stream_add_multicast_client_address:
+ * @stream: a #GstRTSPStream
+ * @destination: (transfer none): a multicast address to add
+ * @rtp_port: RTP port
+ * @rtcp_port: RTCP port
+ * @family: socket family
+ *
+ * Add multicast client address to stream. At this point, the sockets that
+ * will stream RTP and RTCP data to @destination are supposed to be
+ * allocated.
+ *
+ * Returns: %TRUE if @destination can be addedd and handled by @stream.
+ *
+ * Since: 1.16
+ */
+ gboolean
+ gst_rtsp_stream_add_multicast_client_address (GstRTSPStream * stream,
+ const gchar * destination, guint rtp_port, guint rtcp_port,
+ GSocketFamily family)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+ g_return_val_if_fail (destination != NULL, FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ if ((family == G_SOCKET_FAMILY_IPV4) && (priv->mcast_socket_v4[0] == NULL))
+ goto socket_error;
+ else if ((family == G_SOCKET_FAMILY_IPV6) &&
+ (priv->mcast_socket_v6[0] == NULL))
+ goto socket_error;
+
+ if (!add_mcast_client_addr (stream, destination, rtp_port, rtcp_port))
+ goto add_addr_error;
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+
+ socket_error:
+ {
+ GST_WARNING_OBJECT (stream,
+ "Failed to add multicast address: no udp socket");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ add_addr_error:
+ {
+ GST_WARNING_OBJECT (stream,
+ "Failed to add multicast address: invalid address");
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_get_multicast_client_addresses
+ * @stream: a #GstRTSPStream
+ *
+ * Get all multicast client addresses that RTP data will be sent to
+ *
+ * Returns: A comma separated list of host:port pairs with destinations
+ *
+ * Since: 1.16
+ */
+ gchar *
+ gst_rtsp_stream_get_multicast_client_addresses (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GString *str;
+ GList *clients;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+ str = g_string_new ("");
+
+ g_mutex_lock (&priv->lock);
+ clients = priv->mcast_clients;
+ while (clients != NULL) {
+ UdpClientAddrInfo *client;
+
+ client = (UdpClientAddrInfo *) clients->data;
+ clients = g_list_next (clients);
+ g_string_append_printf (str, "%s:%d%s", client->address, client->rtp_port,
+ (clients != NULL ? "," : ""));
+ }
+ g_mutex_unlock (&priv->lock);
+
+ return g_string_free (str, FALSE);
+ }
+
+ /**
+ * gst_rtsp_stream_set_seqnum:
+ * @stream: a #GstRTSPStream
+ * @seqnum: a new sequence number
+ *
+ * Configure the sequence number in the payloader of @stream to @seqnum.
+ */
+ void
+ gst_rtsp_stream_set_seqnum_offset (GstRTSPStream * stream, guint16 seqnum)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ priv = stream->priv;
+
+ g_object_set (G_OBJECT (priv->payloader), "seqnum-offset", seqnum, NULL);
+ }
+
+ /**
+ * gst_rtsp_stream_get_seqnum:
+ * @stream: a #GstRTSPStream
+ *
+ * Get the configured sequence number in the payloader of @stream.
+ *
+ * Returns: the sequence number of the payloader.
+ */
+ guint16
+ gst_rtsp_stream_get_current_seqnum (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ guint seqnum;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), 0);
+
+ priv = stream->priv;
+
+ g_object_get (G_OBJECT (priv->payloader), "seqnum", &seqnum, NULL);
+
+ 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
+ * @func: (scope call) (allow-none): a callback
+ * @user_data: (closure): user data passed to @func
+ *
+ * Call @func for each transport managed by @stream. The result value of @func
+ * determines what happens to the transport. @func will be called with @stream
+ * locked so no further actions on @stream can be performed from @func.
+ *
+ * If @func returns #GST_RTSP_FILTER_REMOVE, the transport will be removed from
+ * @stream.
+ *
+ * If @func returns #GST_RTSP_FILTER_KEEP, the transport will remain in @stream.
+ *
+ * If @func returns #GST_RTSP_FILTER_REF, the transport will remain in @stream but
+ * will also be added with an additional ref to the result #GList of this
+ * function..
+ *
+ * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for each transport.
+ *
+ * Returns: (element-type GstRTSPStreamTransport) (transfer full): a #GList with all
+ * transports for which @func returned #GST_RTSP_FILTER_REF. After usage, each
+ * element in the #GList should be unreffed before the list is freed.
+ */
+ GList *
+ gst_rtsp_stream_transport_filter (GstRTSPStream * stream,
+ GstRTSPStreamTransportFilterFunc func, gpointer user_data)
+ {
+ GstRTSPStreamPrivate *priv;
+ GList *result, *walk, *next;
+ GHashTable *visited = NULL;
+ guint cookie;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ priv = stream->priv;
+
+ result = NULL;
+ if (func)
+ visited = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+
+ g_mutex_lock (&priv->lock);
+ restart:
+ cookie = priv->transports_cookie;
+ for (walk = priv->transports; walk; walk = next) {
+ GstRTSPStreamTransport *trans = walk->data;
+ GstRTSPFilterResult res;
+ gboolean changed;
+
+ next = g_list_next (walk);
+
+ if (func) {
+ /* only visit each transport once */
+ if (g_hash_table_contains (visited, trans))
+ continue;
+
+ g_hash_table_add (visited, g_object_ref (trans));
+ g_mutex_unlock (&priv->lock);
+
+ res = func (stream, trans, user_data);
+
+ g_mutex_lock (&priv->lock);
+ } else
+ res = GST_RTSP_FILTER_REF;
+
+ changed = (cookie != priv->transports_cookie);
+
+ switch (res) {
+ case GST_RTSP_FILTER_REMOVE:
+ update_transport (stream, trans, FALSE);
+ break;
+ case GST_RTSP_FILTER_REF:
+ result = g_list_prepend (result, g_object_ref (trans));
+ break;
+ case GST_RTSP_FILTER_KEEP:
+ default:
+ break;
+ }
+ if (changed)
+ goto restart;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (func)
+ g_hash_table_unref (visited);
+
+ return result;
+ }
+
+ static GstPadProbeReturn
+ pad_blocking (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstRTSPStream *stream;
+ GstBuffer *buffer = NULL;
+ GstPadProbeReturn ret = GST_PAD_PROBE_OK;
+ GstEvent *event;
+
+ stream = user_data;
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ if ((info->type & GST_PAD_PROBE_TYPE_BUFFER)) {
+ GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
+
+ buffer = gst_pad_probe_info_get_buffer (info);
+ if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp)) {
+ priv->blocked_buffer = TRUE;
+ priv->blocked_seqnum = gst_rtp_buffer_get_seq (&rtp);
+ priv->blocked_rtptime = gst_rtp_buffer_get_timestamp (&rtp);
+ gst_rtp_buffer_unmap (&rtp);
+ }
+ priv->position = GST_BUFFER_TIMESTAMP (buffer);
+ } else if ((info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST)) {
+ GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
+
+ GstBufferList *list = gst_pad_probe_info_get_buffer_list (info);
+ buffer = gst_buffer_list_get (list, 0);
+ if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp)) {
+ priv->blocked_buffer = TRUE;
+ priv->blocked_seqnum = gst_rtp_buffer_get_seq (&rtp);
+ priv->blocked_rtptime = gst_rtp_buffer_get_timestamp (&rtp);
+ gst_rtp_buffer_unmap (&rtp);
+ }
+ priv->position = GST_BUFFER_TIMESTAMP (buffer);
+ } else if ((info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)) {
+ if (GST_EVENT_TYPE (info->data) == GST_EVENT_GAP) {
+ gst_event_parse_gap (info->data, &priv->position, NULL);
+ } else {
+ ret = GST_PAD_PROBE_PASS;
+ g_mutex_unlock (&priv->lock);
+ goto done;
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+ event = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
+ if (event) {
+ const GstSegment *segment;
+
+ gst_event_parse_segment (event, &segment);
+ priv->blocked_running_time =
+ gst_segment_to_stream_time (segment, GST_FORMAT_TIME, priv->position);
+ gst_event_unref (event);
+ }
+
+ event = gst_pad_get_sticky_event (pad, GST_EVENT_CAPS, 0);
+ if (event) {
+ GstCaps *caps;
+ GstStructure *s;
+
+ gst_event_parse_caps (event, &caps);
+ s = gst_caps_get_structure (caps, 0);
+ gst_structure_get_int (s, "clock-rate", &priv->blocked_clock_rate);
+ gst_event_unref (event);
+ }
+
+ priv->blocking = TRUE;
+
+ GST_DEBUG_OBJECT (pad, "Now blocking");
+
+ GST_DEBUG_OBJECT (stream, "position: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (priv->position));
+
+ g_mutex_unlock (&priv->lock);
+
+ gst_element_post_message (priv->payloader,
+ gst_message_new_element (GST_OBJECT_CAST (priv->payloader),
+ gst_structure_new ("GstRTSPStreamBlocking", "is_complete",
+ G_TYPE_BOOLEAN, priv->is_complete, NULL)));
+
+ done:
+ return ret;
+ }
+
+ static void
+ set_blocked (GstRTSPStream * stream, gboolean blocked)
+ {
+ GstRTSPStreamPrivate *priv;
+ int i;
+
+ GST_DEBUG_OBJECT (stream, "blocked: %d", blocked);
+
+ priv = stream->priv;
+
+ if (blocked) {
+ /* if receiver */
+ if (priv->sinkpad) {
+ priv->blocking = TRUE;
+ return;
+ }
+ for (i = 0; i < 2; i++) {
+ if (priv->blocked_id[i] != 0)
+ continue;
+ if (priv->send_src[i]) {
+ priv->blocking = FALSE;
+ priv->blocked_buffer = FALSE;
+ priv->blocked_running_time = GST_CLOCK_TIME_NONE;
+ priv->blocked_clock_rate = 0;
+ priv->blocked_id[i] = gst_pad_add_probe (priv->send_src[i],
+ GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER |
+ GST_PAD_PROBE_TYPE_BUFFER_LIST |
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, pad_blocking,
+ g_object_ref (stream), g_object_unref);
+ }
+ }
+ } else {
+ for (i = 0; i < 2; i++) {
+ if (priv->blocked_id[i] != 0) {
+ gst_pad_remove_probe (priv->send_src[i], priv->blocked_id[i]);
+ priv->blocked_id[i] = 0;
+ }
+ }
+ priv->blocking = FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_set_blocked:
+ * @stream: a #GstRTSPStream
+ * @blocked: boolean indicating we should block or unblock
+ *
+ * Blocks or unblocks the dataflow on @stream.
+ *
+ * Returns: %TRUE on success
+ */
+ gboolean
+ gst_rtsp_stream_set_blocked (GstRTSPStream * stream, gboolean blocked)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ set_blocked (stream, blocked);
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_stream_ublock_linked:
+ * @stream: a #GstRTSPStream
+ *
+ * Unblocks the dataflow on @stream if it is linked.
+ *
+ * Returns: %TRUE on success
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_stream_unblock_linked (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ if (priv->send_src[0] && gst_pad_is_linked (priv->send_src[0]))
+ set_blocked (stream, FALSE);
+ g_mutex_unlock (&priv->lock);
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_stream_is_blocking:
+ * @stream: a #GstRTSPStream
+ *
+ * Check if @stream is blocking on a #GstBuffer.
+ *
+ * Returns: %TRUE if @stream is blocking
+ */
+ gboolean
+ gst_rtsp_stream_is_blocking (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean result;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ result = priv->blocking;
+ g_mutex_unlock (&priv->lock);
+
+ return result;
+ }
+
+ /**
+ * gst_rtsp_stream_query_position:
+ * @stream: a #GstRTSPStream
+ * @position: (out): current position of a #GstRTSPStream
+ *
+ * Query the position of the stream in %GST_FORMAT_TIME. This only considers
+ * the RTP parts of the pipeline and not the RTCP parts.
+ *
+ * Returns: %TRUE if the position could be queried
+ */
+ gboolean
+ gst_rtsp_stream_query_position (GstRTSPStream * stream, gint64 * position)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstElement *sink;
+ GstPad *pad = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ /* query position: if no sinks have been added yet,
+ * we obtain the position from the pad otherwise we query the sinks */
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ if (priv->blocking && GST_CLOCK_TIME_IS_VALID (priv->blocked_running_time)) {
+ *position = priv->blocked_running_time;
+ g_mutex_unlock (&priv->lock);
+ return TRUE;
+ }
+
+ /* depending on the transport type, it should query corresponding sink */
+ if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP)
+ sink = priv->udpsink[0];
+ else if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST)
+ sink = priv->mcast_udpsink[0];
+ else
+ sink = priv->appsink[0];
+
+ if (sink) {
+ gst_object_ref (sink);
+ } else if (priv->send_src[0]) {
+ pad = gst_object_ref (priv->send_src[0]);
+ } else {
+ g_mutex_unlock (&priv->lock);
+ GST_WARNING_OBJECT (stream, "Couldn't obtain postion: erroneous pipeline");
+ return FALSE;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (sink) {
+ if (!gst_element_query_position (sink, GST_FORMAT_TIME, position)) {
+ GST_WARNING_OBJECT (stream,
+ "Couldn't obtain postion: position query failed");
+ gst_object_unref (sink);
+ return FALSE;
+ }
+ gst_object_unref (sink);
+ } else if (pad) {
+ GstEvent *event;
+ const GstSegment *segment;
+
+ event = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
+ if (!event) {
+ GST_WARNING_OBJECT (stream, "Couldn't obtain postion: no segment event");
+ gst_object_unref (pad);
+ return FALSE;
+ }
+
+ gst_event_parse_segment (event, &segment);
+ if (segment->format != GST_FORMAT_TIME) {
+ *position = -1;
+ } else {
+ g_mutex_lock (&priv->lock);
+ *position = priv->position;
+ g_mutex_unlock (&priv->lock);
+ *position =
+ gst_segment_to_stream_time (segment, GST_FORMAT_TIME, *position);
+ }
+ gst_event_unref (event);
+ gst_object_unref (pad);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_stream_query_stop:
+ * @stream: a #GstRTSPStream
+ * @stop: (out): current stop of a #GstRTSPStream
+ *
+ * Query the stop of the stream in %GST_FORMAT_TIME. This only considers
+ * the RTP parts of the pipeline and not the RTCP parts.
+ *
+ * Returns: %TRUE if the stop could be queried
+ */
+ gboolean
+ gst_rtsp_stream_query_stop (GstRTSPStream * stream, gint64 * stop)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstElement *sink;
+ GstPad *pad = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ /* query stop position: if no sinks have been added yet,
+ * we obtain the stop position from the pad otherwise we query the sinks */
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ /* depending on the transport type, it should query corresponding sink */
+ if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP)
+ sink = priv->udpsink[0];
+ else if (priv->configured_protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST)
+ sink = priv->mcast_udpsink[0];
+ else
+ sink = priv->appsink[0];
+
+ if (sink) {
+ gst_object_ref (sink);
+ } else if (priv->send_src[0]) {
+ pad = gst_object_ref (priv->send_src[0]);
+ } else {
+ g_mutex_unlock (&priv->lock);
+ GST_WARNING_OBJECT (stream, "Couldn't obtain stop: erroneous pipeline");
+ return FALSE;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ if (sink) {
+ GstQuery *query;
+ GstFormat format;
+ gdouble rate;
+ gint64 start_value;
+ gint64 stop_value;
+
+ query = gst_query_new_segment (GST_FORMAT_TIME);
+ if (!gst_element_query (sink, query)) {
+ GST_WARNING_OBJECT (stream, "Couldn't obtain stop: element query failed");
+ gst_query_unref (query);
+ gst_object_unref (sink);
+ return FALSE;
+ }
+ gst_query_parse_segment (query, &rate, &format, &start_value, &stop_value);
+ if (format != GST_FORMAT_TIME)
+ *stop = -1;
+ else
+ *stop = rate > 0.0 ? stop_value : start_value;
+ gst_query_unref (query);
+ gst_object_unref (sink);
+ } else if (pad) {
+ GstEvent *event;
+ const GstSegment *segment;
+
+ event = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
+ if (!event) {
+ GST_WARNING_OBJECT (stream, "Couldn't obtain stop: no segment event");
+ gst_object_unref (pad);
+ return FALSE;
+ }
+ gst_event_parse_segment (event, &segment);
+ if (segment->format != GST_FORMAT_TIME) {
+ *stop = -1;
+ } else {
+ *stop = segment->stop;
+ if (*stop == -1)
+ *stop = segment->duration;
+ else
+ *stop = gst_segment_to_stream_time (segment, GST_FORMAT_TIME, *stop);
+ }
+ gst_event_unref (event);
+ gst_object_unref (pad);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * gst_rtsp_stream_seekable:
+ * @stream: a #GstRTSPStream
+ *
+ * Checks whether the individual @stream is seekable.
+ *
+ * Returns: %TRUE if @stream is seekable, else %FALSE.
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_stream_seekable (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ GstPad *pad = NULL;
+ GstQuery *query = NULL;
+ gboolean seekable = FALSE;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ /* query stop position: if no sinks have been added yet,
+ * we obtain the stop position from the pad otherwise we query the sinks */
+
+ priv = stream->priv;
+
+ g_mutex_lock (&priv->lock);
+ /* depending on the transport type, it should query corresponding sink */
+ if (priv->srcpad) {
+ pad = gst_object_ref (priv->srcpad);
+ } else {
+ g_mutex_unlock (&priv->lock);
+ GST_WARNING_OBJECT (stream, "Pad not available, can't query seekability");
+ goto beach;
+ }
+ g_mutex_unlock (&priv->lock);
+
+ query = gst_query_new_seeking (GST_FORMAT_TIME);
+ if (!gst_pad_query (pad, query)) {
+ GST_WARNING_OBJECT (stream, "seeking query failed");
+ goto beach;
+ }
+ gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
+
+ beach:
+ if (pad)
+ gst_object_unref (pad);
+ if (query)
+ gst_query_unref (query);
+
+ GST_DEBUG_OBJECT (stream, "Returning %d", seekable);
+
+ return seekable;
+ }
+
+ /**
+ * gst_rtsp_stream_complete_stream:
+ * @stream: a #GstRTSPStream
+ * @transport: a #GstRTSPTransport
+ *
+ * Add a receiver and sender part to the pipeline based on the transport from
+ * SETUP.
+ *
+ * Returns: %TRUE if the stream has been sucessfully updated.
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_stream_complete_stream (GstRTSPStream * stream,
+ const GstRTSPTransport * transport)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ GST_DEBUG_OBJECT (stream, "complete stream");
+
+ g_mutex_lock (&priv->lock);
+
+ if (!(priv->allowed_protocols & transport->lower_transport))
+ goto unallowed_transport;
+
+ if (!create_receiver_part (stream, transport))
+ goto create_receiver_error;
+
+ /* in the RECORD case, we only add RTCP sender part */
+ if (!create_sender_part (stream, transport))
+ goto create_sender_error;
+
+ priv->configured_protocols |= transport->lower_transport;
+
+ priv->is_complete = TRUE;
+ g_mutex_unlock (&priv->lock);
+
+ GST_DEBUG_OBJECT (stream, "pipeline sucsessfully updated");
+ return TRUE;
+
+ create_receiver_error:
+ create_sender_error:
+ unallowed_transport:
+ {
+ g_mutex_unlock (&priv->lock);
+ return FALSE;
+ }
+ }
+
+ /**
+ * gst_rtsp_stream_is_complete:
+ * @stream: a #GstRTSPStream
+ *
+ * Checks whether the stream is complete, contains the receiver and the sender
+ * parts. As the stream contains sink(s) element(s), it's possible to perform
+ * seek operations on it.
+ *
+ * Returns: %TRUE if the stream contains at least one sink element.
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_stream_is_complete (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ ret = priv->is_complete;
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_is_sender:
+ * @stream: a #GstRTSPStream
+ *
+ * Checks whether the stream is a sender.
+ *
+ * Returns: %TRUE if the stream is a sender and %FALSE otherwise.
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_stream_is_sender (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ ret = (priv->srcpad != NULL);
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_is_receiver:
+ * @stream: a #GstRTSPStream
+ *
+ * Checks whether the stream is a receiver.
+ *
+ * Returns: %TRUE if the stream is a receiver and %FALSE otherwise.
+ *
+ * Since: 1.14
+ */
+ gboolean
+ gst_rtsp_stream_is_receiver (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), FALSE);
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ ret = (priv->sinkpad != NULL);
+ g_mutex_unlock (&priv->lock);
+
+ return ret;
+ }
+
+ #define AES_128_KEY_LEN 16
+ #define AES_256_KEY_LEN 32
+
+ #define HMAC_32_KEY_LEN 4
+ #define HMAC_80_KEY_LEN 10
+
+ static gboolean
+ mikey_apply_policy (GstCaps * caps, GstMIKEYMessage * msg, guint8 policy)
+ {
+ const gchar *srtp_cipher;
+ const gchar *srtp_auth;
+ const GstMIKEYPayload *sp;
+ guint i;
+
+ /* loop over Security policy until we find one containing policy */
+ for (i = 0;; i++) {
+ if ((sp = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, i)) == NULL)
+ break;
+
+ if (((GstMIKEYPayloadSP *) sp)->policy == policy)
+ break;
+ }
+
+ /* the default ciphers */
+ srtp_cipher = "aes-128-icm";
+ srtp_auth = "hmac-sha1-80";
+
+ /* now override the defaults with what is in the Security Policy */
+ if (sp != NULL) {
+ guint len;
+ guint enc_alg = GST_MIKEY_ENC_AES_CM_128;
+
+ /* collect all the params and go over them */
+ len = gst_mikey_payload_sp_get_n_params (sp);
+ for (i = 0; i < len; i++) {
+ const GstMIKEYPayloadSPParam *param =
+ gst_mikey_payload_sp_get_param (sp, i);
+
+ switch (param->type) {
+ case GST_MIKEY_SP_SRTP_ENC_ALG:
+ enc_alg = param->val[0];
+ switch (param->val[0]) {
+ case GST_MIKEY_ENC_NULL:
+ srtp_cipher = "null";
+ break;
+ case GST_MIKEY_ENC_AES_CM_128:
+ case GST_MIKEY_ENC_AES_KW_128:
+ srtp_cipher = "aes-128-icm";
+ break;
+ case GST_MIKEY_ENC_AES_GCM_128:
+ srtp_cipher = "aes-128-gcm";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_ENC_KEY_LEN:
+ switch (param->val[0]) {
+ case AES_128_KEY_LEN:
+ if (enc_alg == GST_MIKEY_ENC_AES_CM_128 ||
+ enc_alg == GST_MIKEY_ENC_AES_KW_128) {
+ srtp_cipher = "aes-128-icm";
+ } else if (enc_alg == GST_MIKEY_ENC_AES_GCM_128) {
+ srtp_cipher = "aes-128-gcm";
+ }
+ break;
+ case AES_256_KEY_LEN:
+ if (enc_alg == GST_MIKEY_ENC_AES_CM_128 ||
+ enc_alg == GST_MIKEY_ENC_AES_KW_128) {
+ srtp_cipher = "aes-256-icm";
+ } else if (enc_alg == GST_MIKEY_ENC_AES_GCM_128) {
+ srtp_cipher = "aes-256-gcm";
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_AUTH_ALG:
+ switch (param->val[0]) {
+ case GST_MIKEY_MAC_NULL:
+ srtp_auth = "null";
+ break;
+ case GST_MIKEY_MAC_HMAC_SHA_1_160:
+ srtp_auth = "hmac-sha1-80";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN:
+ switch (param->val[0]) {
+ case HMAC_32_KEY_LEN:
+ srtp_auth = "hmac-sha1-32";
+ break;
+ case HMAC_80_KEY_LEN:
+ srtp_auth = "hmac-sha1-80";
+ break;
+ default:
+ break;
+ }
+ break;
+ case GST_MIKEY_SP_SRTP_SRTP_ENC:
+ break;
+ case GST_MIKEY_SP_SRTP_SRTCP_ENC:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ /* now configure the SRTP parameters */
+ gst_caps_set_simple (caps,
+ "srtp-cipher", G_TYPE_STRING, srtp_cipher,
+ "srtp-auth", G_TYPE_STRING, srtp_auth,
+ "srtcp-cipher", G_TYPE_STRING, srtp_cipher,
+ "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL);
+
+ return TRUE;
+ }
+
+ static gboolean
+ handle_mikey_data (GstRTSPStream * stream, guint8 * data, gsize size)
+ {
+ GstMIKEYMessage *msg;
+ guint i, n_cs;
+ GstCaps *caps = NULL;
+ GstMIKEYPayloadKEMAC *kemac;
+ const GstMIKEYPayloadKeyData *pkd;
+ GstBuffer *key;
+
+ /* the MIKEY message contains a CSB or crypto session bundle. It is a
+ * set of Crypto Sessions protected with the same master key.
+ * In the context of SRTP, an RTP and its RTCP stream is part of a
+ * crypto session */
+ if ((msg = gst_mikey_message_new_from_data (data, size, NULL, NULL)) == NULL)
+ goto parse_failed;
+
+ /* we can only handle SRTP crypto sessions for now */
+ if (msg->map_type != GST_MIKEY_MAP_TYPE_SRTP)
+ goto invalid_map_type;
+
+ /* get the number of crypto sessions. This maps SSRC to its
+ * security parameters */
+ n_cs = gst_mikey_message_get_n_cs (msg);
+ if (n_cs == 0)
+ goto no_crypto_sessions;
+
+ /* we also need keys */
+ if (!(kemac = (GstMIKEYPayloadKEMAC *) gst_mikey_message_find_payload
+ (msg, GST_MIKEY_PT_KEMAC, 0)))
+ goto no_keys;
+
+ /* we don't support encrypted keys */
+ if (kemac->enc_alg != GST_MIKEY_ENC_NULL
+ || kemac->mac_alg != GST_MIKEY_MAC_NULL)
+ goto unsupported_encryption;
+
+ /* get Key data sub-payload */
+ pkd = (const GstMIKEYPayloadKeyData *)
+ gst_mikey_payload_kemac_get_sub (&kemac->pt, 0);
+
+ key = gst_buffer_new_memdup (pkd->key_data, pkd->key_len);
+
+ /* go over all crypto sessions and create the security policy for each
+ * SSRC */
+ for (i = 0; i < n_cs; i++) {
+ const GstMIKEYMapSRTP *map = gst_mikey_message_get_cs_srtp (msg, i);
+
+ caps = gst_caps_new_simple ("application/x-srtp",
+ "ssrc", G_TYPE_UINT, map->ssrc,
+ "roc", G_TYPE_UINT, map->roc, "srtp-key", GST_TYPE_BUFFER, key, NULL);
+ mikey_apply_policy (caps, msg, map->policy);
+
+ gst_rtsp_stream_update_crypto (stream, map->ssrc, caps);
+ gst_caps_unref (caps);
+ }
+ gst_mikey_message_unref (msg);
+ gst_buffer_unref (key);
+
+ return TRUE;
+
+ /* ERRORS */
+ parse_failed:
+ {
+ GST_DEBUG_OBJECT (stream, "failed to parse MIKEY message");
+ return FALSE;
+ }
+ invalid_map_type:
+ {
+ GST_DEBUG_OBJECT (stream, "invalid map type %d", msg->map_type);
+ goto cleanup_message;
+ }
+ no_crypto_sessions:
+ {
+ GST_DEBUG_OBJECT (stream, "no crypto sessions");
+ goto cleanup_message;
+ }
+ no_keys:
+ {
+ GST_DEBUG_OBJECT (stream, "no keys found");
+ goto cleanup_message;
+ }
+ unsupported_encryption:
+ {
+ GST_DEBUG_OBJECT (stream, "unsupported key encryption");
+ goto cleanup_message;
+ }
+ cleanup_message:
+ {
+ gst_mikey_message_unref (msg);
+ return FALSE;
+ }
+ }
+
+ #define IS_STRIP_CHAR(c) (g_ascii_isspace ((guchar)(c)) || ((c) == '\"'))
+
+ static void
+ strip_chars (gchar * str)
+ {
+ gchar *s;
+ gsize len;
+
+ len = strlen (str);
+ while (len--) {
+ if (!IS_STRIP_CHAR (str[len]))
+ break;
+ str[len] = '\0';
+ }
+ for (s = str; *s && IS_STRIP_CHAR (*s); s++);
+ memmove (str, s, len + 1);
+ }
+
+ /**
+ * gst_rtsp_stream_handle_keymgmt:
+ * @stream: a #GstRTSPStream
+ * @keymgmt: a keymgmt header
+ *
+ * Parse and handle a KeyMgmt header.
+ *
+ * Since: 1.16
+ */
+ /* KeyMgmt = "KeyMgmt" ":" key-mgmt-spec 0*("," key-mgmt-spec)
+ * key-mgmt-spec = "prot" "=" KMPID ";" ["uri" "=" %x22 URI %x22 ";"]
+ */
+ gboolean
+ gst_rtsp_stream_handle_keymgmt (GstRTSPStream * stream, const gchar * keymgmt)
+ {
+ gchar **specs;
+ gint i, j;
+
+ specs = g_strsplit (keymgmt, ",", 0);
+ for (i = 0; specs[i]; i++) {
+ gchar **split;
+
+ split = g_strsplit (specs[i], ";", 0);
+ for (j = 0; split[j]; j++) {
+ g_strstrip (split[j]);
+ if (g_str_has_prefix (split[j], "prot=")) {
+ g_strstrip (split[j] + 5);
+ if (!g_str_equal (split[j] + 5, "mikey"))
+ break;
+ GST_DEBUG ("found mikey");
+ } else if (g_str_has_prefix (split[j], "uri=")) {
+ strip_chars (split[j] + 4);
+ GST_DEBUG ("found uri '%s'", split[j] + 4);
+ } else if (g_str_has_prefix (split[j], "data=")) {
+ guchar *data;
+ gsize size;
+ strip_chars (split[j] + 5);
+ GST_DEBUG ("found data '%s'", split[j] + 5);
+ data = g_base64_decode_inplace (split[j] + 5, &size);
+ handle_mikey_data (stream, data, size);
+ }
+ }
+ g_strfreev (split);
+ }
+ g_strfreev (specs);
+ return TRUE;
+ }
+
+
+ /**
+ * gst_rtsp_stream_get_ulpfec_pt:
+ *
+ * Returns: the payload type used for ULPFEC protection packets
+ *
+ * Since: 1.16
+ */
+ guint
+ gst_rtsp_stream_get_ulpfec_pt (GstRTSPStream * stream)
+ {
+ guint res;
+
+ g_mutex_lock (&stream->priv->lock);
+ res = stream->priv->ulpfec_pt;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_set_ulpfec_pt:
+ *
+ * Set the payload type to be used for ULPFEC protection packets
+ *
+ * Since: 1.16
+ */
+ void
+ gst_rtsp_stream_set_ulpfec_pt (GstRTSPStream * stream, guint pt)
+ {
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->ulpfec_pt = pt;
+ if (stream->priv->ulpfec_encoder) {
+ g_object_set (stream->priv->ulpfec_encoder, "pt", pt, NULL);
+ }
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_request_ulpfec_decoder:
+ *
+ * Creating a rtpulpfecdec element
+ *
+ * Returns: (transfer full) (nullable): a #GstElement.
+ *
+ * Since: 1.16
+ */
+ GstElement *
+ gst_rtsp_stream_request_ulpfec_decoder (GstRTSPStream * stream,
+ GstElement * rtpbin, guint sessid)
+ {
+ GObject *internal_storage = NULL;
+
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+ stream->priv->ulpfec_decoder =
+ gst_object_ref (gst_element_factory_make ("rtpulpfecdec", NULL));
+
+ g_signal_emit_by_name (G_OBJECT (rtpbin), "get-internal-storage", sessid,
+ &internal_storage);
+ g_object_set (stream->priv->ulpfec_decoder, "storage", internal_storage,
+ NULL);
+ g_object_unref (internal_storage);
+ update_ulpfec_decoder_pt (stream);
+
+ return stream->priv->ulpfec_decoder;
+ }
+
+ /**
+ * gst_rtsp_stream_request_ulpfec_encoder:
+ *
+ * Creating a rtpulpfecenc element
+ *
+ * Returns: (transfer full) (nullable): a #GstElement.
+ *
+ * Since: 1.16
+ */
+ GstElement *
+ gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream * stream, guint sessid)
+ {
+ g_return_val_if_fail (GST_IS_RTSP_STREAM (stream), NULL);
+
+ if (!stream->priv->ulpfec_percentage)
+ return NULL;
+
+ stream->priv->ulpfec_encoder =
+ gst_object_ref (gst_element_factory_make ("rtpulpfecenc", NULL));
+
+ g_object_set (stream->priv->ulpfec_encoder, "pt", stream->priv->ulpfec_pt,
+ "percentage", stream->priv->ulpfec_percentage, NULL);
+
+ return stream->priv->ulpfec_encoder;
+ }
+
+ /**
+ * gst_rtsp_stream_set_ulpfec_percentage:
+ *
+ * Sets the amount of redundancy to apply when creating ULPFEC
+ * protection packets.
+ *
+ * Since: 1.16
+ */
+ void
+ gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream * stream, guint percentage)
+ {
+ g_return_if_fail (GST_IS_RTSP_STREAM (stream));
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->ulpfec_percentage = percentage;
+ if (stream->priv->ulpfec_encoder) {
+ g_object_set (stream->priv->ulpfec_encoder, "percentage", percentage, NULL);
+ }
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_ulpfec_percentage:
+ *
+ * Returns: the amount of redundancy applied when creating ULPFEC
+ * protection packets.
+ *
+ * Since: 1.16
+ */
+ guint
+ gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream * stream)
+ {
+ guint res;
+
+ g_mutex_lock (&stream->priv->lock);
+ res = stream->priv->ulpfec_percentage;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return res;
+ }
+
+ /**
+ * gst_rtsp_stream_set_rate_control:
+ *
+ * Define whether @stream will follow the Rate-Control=no behaviour as specified
+ * in the ONVIF replay spec.
+ *
+ * Since: 1.18
+ */
+ void
+ gst_rtsp_stream_set_rate_control (GstRTSPStream * stream, gboolean enabled)
+ {
+ GST_DEBUG_OBJECT (stream, "%s rate control",
+ enabled ? "Enabling" : "Disabling");
+
+ g_mutex_lock (&stream->priv->lock);
+ stream->priv->do_rate_control = enabled;
+ if (stream->priv->appsink[0])
+ g_object_set (stream->priv->appsink[0], "sync", enabled, NULL);
+ if (stream->priv->payloader
+ && g_object_class_find_property (G_OBJECT_GET_CLASS (stream->
+ priv->payloader), "onvif-no-rate-control"))
+ g_object_set (stream->priv->payloader, "onvif-no-rate-control", !enabled,
+ NULL);
+ if (stream->priv->session) {
+ g_object_set (stream->priv->session, "disable-sr-timestamp", !enabled,
+ NULL);
+ }
+ g_mutex_unlock (&stream->priv->lock);
+ }
+
+ /**
+ * gst_rtsp_stream_get_rate_control:
+ *
+ * Returns: whether @stream will follow the Rate-Control=no behaviour as specified
+ * in the ONVIF replay spec.
+ *
+ * Since: 1.18
+ */
+ gboolean
+ gst_rtsp_stream_get_rate_control (GstRTSPStream * stream)
+ {
+ gboolean ret;
+
+ g_mutex_lock (&stream->priv->lock);
+ ret = stream->priv->do_rate_control;
+ g_mutex_unlock (&stream->priv->lock);
+
+ return ret;
+ }
+
+ /**
+ * gst_rtsp_stream_unblock_rtcp:
+ *
+ * Remove blocking probe from the RTCP source. When creating an UDP source for
+ * RTCP it is initially blocked until this function is called.
+ * This functions should be called once the pipeline is ready for handling RTCP
+ * packets.
+ *
+ * Since: 1.20
+ */
+ void
+ gst_rtsp_stream_unblock_rtcp (GstRTSPStream * stream)
+ {
+ GstRTSPStreamPrivate *priv;
+
+ priv = stream->priv;
+ g_mutex_lock (&priv->lock);
+ if (priv->block_early_rtcp_probe != 0) {
+ gst_pad_remove_probe
+ (priv->block_early_rtcp_pad, priv->block_early_rtcp_probe);
+ priv->block_early_rtcp_probe = 0;
+ gst_object_unref (priv->block_early_rtcp_pad);
+ priv->block_early_rtcp_pad = NULL;
+ }
+ if (priv->block_early_rtcp_probe_ipv6 != 0) {
+ gst_pad_remove_probe
+ (priv->block_early_rtcp_pad_ipv6, priv->block_early_rtcp_probe_ipv6);
+ priv->block_early_rtcp_probe_ipv6 = 0;
+ gst_object_unref (priv->block_early_rtcp_pad_ipv6);
+ priv->block_early_rtcp_pad_ipv6 = NULL;
+ }
+ g_mutex_unlock (&priv->lock);
+ }
--- /dev/null
+ /* GStreamer
+ * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
+ *
+ * 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 <gst/gst.h>
+ #include <gst/rtsp/rtsp.h>
+ #include <gio/gio.h>
+
+ #ifndef __GST_RTSP_STREAM_H__
+ #define __GST_RTSP_STREAM_H__
+
+ #include "rtsp-server-prelude.h"
+
+ G_BEGIN_DECLS
+
+ /* types for the media stream */
+ #define GST_TYPE_RTSP_STREAM (gst_rtsp_stream_get_type ())
+ #define GST_IS_RTSP_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTSP_STREAM))
+ #define GST_IS_RTSP_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTSP_STREAM))
+ #define GST_RTSP_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTSP_STREAM, GstRTSPStreamClass))
+ #define GST_RTSP_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTSP_STREAM, GstRTSPStream))
+ #define GST_RTSP_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTSP_STREAM, GstRTSPStreamClass))
+ #define GST_RTSP_STREAM_CAST(obj) ((GstRTSPStream*)(obj))
+ #define GST_RTSP_STREAM_CLASS_CAST(klass) ((GstRTSPStreamClass*)(klass))
+
+ typedef struct _GstRTSPStream GstRTSPStream;
+ typedef struct _GstRTSPStreamClass GstRTSPStreamClass;
+ typedef struct _GstRTSPStreamPrivate GstRTSPStreamPrivate;
+
+ #include "rtsp-stream-transport.h"
+ #include "rtsp-address-pool.h"
+ #include "rtsp-session.h"
+ #include "rtsp-media.h"
+
+ /**
+ * GstRTSPStream:
+ *
+ * The definition of a media stream.
+ */
+ struct _GstRTSPStream {
+ GObject parent;
+
+ /*< private >*/
+ GstRTSPStreamPrivate *priv;
+ gpointer _gst_reserved[GST_PADDING];
+ };
+
+ struct _GstRTSPStreamClass {
+ GObjectClass parent_class;
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING];
+ };
+
+ GST_RTSP_SERVER_API
+ GType gst_rtsp_stream_get_type (void);
+
+ GST_RTSP_SERVER_API
+ GstRTSPStream * gst_rtsp_stream_new (guint idx, GstElement *payloader,
+ GstPad *pad);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_index (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_pt (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ GstPad * gst_rtsp_stream_get_srcpad (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ GstPad * gst_rtsp_stream_get_sinkpad (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_control (GstRTSPStream *stream, const gchar *control);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_stream_get_control (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_has_control (GstRTSPStream *stream, const gchar *control);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_mtu (GstRTSPStream *stream, guint mtu);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_mtu (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_dscp_qos (GstRTSPStream *stream, gint dscp_qos);
+
+ GST_RTSP_SERVER_API
+ gint gst_rtsp_stream_get_dscp_qos (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_transport_supported (GstRTSPStream *stream,
+ GstRTSPTransport *transport);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_profiles (GstRTSPStream *stream, GstRTSPProfile profiles);
+
+ GST_RTSP_SERVER_API
+ GstRTSPProfile gst_rtsp_stream_get_profiles (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_protocols (GstRTSPStream *stream, GstRTSPLowerTrans protocols);
+
+ GST_RTSP_SERVER_API
+ GstRTSPLowerTrans gst_rtsp_stream_get_protocols (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_address_pool (GstRTSPStream *stream, GstRTSPAddressPool *pool);
+
+ GST_RTSP_SERVER_API
+ GstRTSPAddressPool *
+ gst_rtsp_stream_get_address_pool (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_multicast_iface (GstRTSPStream *stream, const gchar * multicast_iface);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_stream_get_multicast_iface (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ GstRTSPAddress * gst_rtsp_stream_reserve_address (GstRTSPStream *stream,
+ const gchar * address,
+ guint port,
+ guint n_ports,
+ guint ttl);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_join_bin (GstRTSPStream *stream,
+ GstBin *bin, GstElement *rtpbin,
+ GstState state);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_leave_bin (GstRTSPStream *stream,
+ GstBin *bin, GstElement *rtpbin);
+
+ GST_RTSP_SERVER_API
+ GstBin * gst_rtsp_stream_get_joined_bin (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_set_blocked (GstRTSPStream * stream,
+ gboolean blocked);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_blocking (GstRTSPStream * stream);
+
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_unblock_linked (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_client_side (GstRTSPStream *stream, gboolean client_side);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_client_side (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_get_server_port (GstRTSPStream *stream,
+ GstRTSPRange *server_port,
+ GSocketFamily family);
+
+ GST_RTSP_SERVER_API
+ GstRTSPAddress * gst_rtsp_stream_get_multicast_address (GstRTSPStream *stream,
+ GSocketFamily family);
+
+
+ GST_RTSP_SERVER_API
+ GObject * gst_rtsp_stream_get_rtpsession (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ GstElement * gst_rtsp_stream_get_srtp_encoder (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_get_ssrc (GstRTSPStream *stream,
+ guint *ssrc);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_get_rtpinfo (GstRTSPStream *stream,
+ guint *rtptime, guint *seq,
+ guint *clock_rate,
+ GstClockTime *running_time);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_get_rates (GstRTSPStream * stream,
+ gdouble * rate,
+ gdouble * applied_rate);
+
+ GST_RTSP_SERVER_API
+ GstCaps * gst_rtsp_stream_get_caps (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ GstFlowReturn gst_rtsp_stream_recv_rtp (GstRTSPStream *stream,
+ GstBuffer *buffer);
+
+ GST_RTSP_SERVER_API
+ GstFlowReturn gst_rtsp_stream_recv_rtcp (GstRTSPStream *stream,
+ GstBuffer *buffer);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_add_transport (GstRTSPStream *stream,
+ GstRTSPStreamTransport *trans);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_remove_transport (GstRTSPStream *stream,
+ GstRTSPStreamTransport *trans);
+
+ GST_RTSP_SERVER_API
+ GSocket * gst_rtsp_stream_get_rtp_socket (GstRTSPStream *stream,
+ GSocketFamily family);
+
+ GST_RTSP_SERVER_API
+ GSocket * gst_rtsp_stream_get_rtcp_socket (GstRTSPStream *stream,
+ GSocketFamily family);
+
+ GST_RTSP_SERVER_API
+ GSocket * gst_rtsp_stream_get_rtp_multicast_socket (GstRTSPStream *stream,
+ GSocketFamily family);
+
+ GST_RTSP_SERVER_API
+ GSocket * gst_rtsp_stream_get_rtcp_multicast_socket (GstRTSPStream *stream,
+ GSocketFamily family);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_add_multicast_client_address (GstRTSPStream * stream,
+ const gchar * destination,
+ guint rtp_port,
+ guint rtcp_port,
+ GSocketFamily family);
+
+ GST_RTSP_SERVER_API
+ gchar * gst_rtsp_stream_get_multicast_client_addresses (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_update_crypto (GstRTSPStream * stream,
+ guint ssrc, GstCaps * crypto);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_query_position (GstRTSPStream * stream,
+ gint64 * position);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_query_stop (GstRTSPStream * stream,
+ gint64 * stop);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_seekable (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_seqnum_offset (GstRTSPStream *stream, guint16 seqnum);
+
+ GST_RTSP_SERVER_API
+ guint16 gst_rtsp_stream_get_current_seqnum (GstRTSPStream *stream);
+
++GST_RTSP_SERVER_API
++guint64 gst_rtsp_stream_get_udp_sent_bytes (GstRTSPStream *stream);
++
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_retransmission_time (GstRTSPStream *stream, GstClockTime time);
+
+ GST_RTSP_SERVER_API
+ GstClockTime gst_rtsp_stream_get_retransmission_time (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_retransmission_pt (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_retransmission_pt (GstRTSPStream * stream,
+ guint rtx_pt);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_buffer_size (GstRTSPStream *stream, guint size);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_buffer_size (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_pt_map (GstRTSPStream * stream, guint pt, GstCaps * caps);
+
+ GST_RTSP_SERVER_API
+ GstElement * gst_rtsp_stream_request_aux_sender (GstRTSPStream * stream, guint sessid);
+
+ GST_RTSP_SERVER_API
+ GstElement * gst_rtsp_stream_request_aux_receiver (GstRTSPStream * stream, guint sessid);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_allocate_udp_sockets (GstRTSPStream * stream, GSocketFamily family,
+ GstRTSPTransport *transport, gboolean use_client_settings);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_publish_clock_mode (GstRTSPStream * stream, GstRTSPPublishClockMode mode);
+
+ GST_RTSP_SERVER_API
+ GstRTSPPublishClockMode gst_rtsp_stream_get_publish_clock_mode (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_set_max_mcast_ttl (GstRTSPStream *stream, guint ttl);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_max_mcast_ttl (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_verify_mcast_ttl (GstRTSPStream *stream, guint ttl);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_bind_mcast_address (GstRTSPStream * stream, gboolean bind_mcast_addr);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_bind_mcast_address (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_complete_stream (GstRTSPStream * stream, const GstRTSPTransport * transport);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_complete (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_sender (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_is_receiver (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_handle_keymgmt (GstRTSPStream *stream, const gchar *keymgmt);
+
+ /* ULP Forward Error Correction (RFC 5109) */
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_get_ulpfec_enabled (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_ulpfec_pt (GstRTSPStream *stream, guint pt);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_ulpfec_pt (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ GstElement * gst_rtsp_stream_request_ulpfec_decoder (GstRTSPStream *stream, GstElement *rtpbin, guint sessid);
+
+ GST_RTSP_SERVER_API
+ GstElement * gst_rtsp_stream_request_ulpfec_encoder (GstRTSPStream *stream, guint sessid);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_ulpfec_percentage (GstRTSPStream *stream, guint percentage);
+
+ GST_RTSP_SERVER_API
+ guint gst_rtsp_stream_get_ulpfec_percentage (GstRTSPStream *stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_set_rate_control (GstRTSPStream * stream, gboolean enabled);
+
+ GST_RTSP_SERVER_API
+ gboolean gst_rtsp_stream_get_rate_control (GstRTSPStream * stream);
+
+ GST_RTSP_SERVER_API
+ void gst_rtsp_stream_unblock_rtcp (GstRTSPStream * stream);
+
+ /**
+ * GstRTSPStreamTransportFilterFunc:
+ * @stream: a #GstRTSPStream object
+ * @trans: a #GstRTSPStreamTransport in @stream
+ * @user_data: user data that has been given to gst_rtsp_stream_transport_filter()
+ *
+ * This function will be called by the gst_rtsp_stream_transport_filter(). An
+ * implementation should return a value of #GstRTSPFilterResult.
+ *
+ * When this function returns #GST_RTSP_FILTER_REMOVE, @trans will be removed
+ * from @stream.
+ *
+ * A return value of #GST_RTSP_FILTER_KEEP will leave @trans untouched in
+ * @stream.
+ *
+ * A value of #GST_RTSP_FILTER_REF will add @trans to the result #GList of
+ * gst_rtsp_stream_transport_filter().
+ *
+ * Returns: a #GstRTSPFilterResult.
+ */
+ typedef GstRTSPFilterResult (*GstRTSPStreamTransportFilterFunc) (GstRTSPStream *stream,
+ GstRTSPStreamTransport *trans,
+ gpointer user_data);
+
+ GST_RTSP_SERVER_API
+ GList * gst_rtsp_stream_transport_filter (GstRTSPStream *stream,
+ GstRTSPStreamTransportFilterFunc func,
+ gpointer user_data);
+
+ #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstRTSPStream, gst_object_unref)
+ #endif
+
+ G_END_DECLS
+
+ #endif /* __GST_RTSP_STREAM_H__ */
--- /dev/null
- if (sink->conninfo.connection == NULL)
+ /* GStreamer
+ * Copyright (C) <2005,2006> Wim Taymans <wim at fluendo dot com>
+ * <2006> Lutz Mueller <lutz at topfrose dot de>
+ * <2015> Jan Schmidt <jan at centricular dot com>
+ *
+ * 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:element-rtspclientsink
+ *
+ * Makes a connection to an RTSP server and send data via RTSP RECORD.
+ * rtspclientsink strictly follows RFC 2326
+ *
+ * RTSP supports transport over TCP or UDP in unicast or multicast mode. By
+ * default rtspclientsink will negotiate a connection in the following order:
+ * UDP unicast/UDP multicast/TCP. The order cannot be changed but the allowed
+ * protocols can be controlled with the #GstRTSPClientSink:protocols property.
+ *
+ * rtspclientsink will internally instantiate an RTP session manager element
+ * that will handle the RTCP messages to and from the server, jitter removal,
+ * and packet reordering.
+ * This feature is implemented using the gstrtpbin element.
+ *
+ * rtspclientsink accepts any stream for which there is an installed payloader,
+ * creates the payloader and manages payload-types, as well as RTX setup.
+ * The new-payloader signal is fired when a payloader is created, in case
+ * an app wants to do custom configuration (such as for MTU).
+ *
+ * ## Example launch line
+ *
+ * |[
+ * gst-launch-1.0 videotestsrc ! jpegenc ! rtspclientsink location=rtsp://some.server/url
+ * ]| Establish a connection to an RTSP server and send JPEG encoded video packets
+ */
+
+ /* FIXMEs
+ * - Handle EOS properly and shutdown. The problem with EOS is we don't know
+ * when the server has received all data, so we don't know when to do teardown.
+ * At the moment, we forward EOS to the app as soon as we stop sending. Is there
+ * a way to know from the receiver that it's got all data? Some session timeout?
+ * - Implement extension support for Real / WMS if they support RECORD?
+ * - Add support for network clock synchronised streaming?
+ * - Fix crypto key nego so SAVP/SAVPF profiles work.
+ * - Test (&fix?) HTTP tunnel support
+ * - Add an address pool object for GstRTSPStreams to use for multicast
+ * - Test multicast UDP transport
+ */
+
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+
+ #ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+ #endif /* HAVE_UNISTD_H */
+ #include <stdlib.h>
+ #include <string.h>
+ #include <stdio.h>
+ #include <stdarg.h>
+
+ #include <gst/net/gstnet.h>
+ #include <gst/sdp/gstsdpmessage.h>
+ #include <gst/sdp/gstmikey.h>
+ #include <gst/rtp/rtp.h>
+
+ #include "gstrtspclientsink.h"
+
+ typedef struct _GstRtspClientSinkPad GstRtspClientSinkPad;
+ typedef GstGhostPadClass GstRtspClientSinkPadClass;
+
+ struct _GstRtspClientSinkPad
+ {
+ GstGhostPad parent;
+ GstElement *custom_payloader;
+ guint ulpfec_percentage;
+ };
+
+ enum
+ {
+ PROP_PAD_0,
+ PROP_PAD_PAYLOADER,
+ PROP_PAD_ULPFEC_PERCENTAGE
+ };
+
+ #define DEFAULT_PAD_ULPFEC_PERCENTAGE 0
+
+ static GType gst_rtsp_client_sink_pad_get_type (void);
+ G_DEFINE_TYPE (GstRtspClientSinkPad, gst_rtsp_client_sink_pad,
+ GST_TYPE_GHOST_PAD);
+ #define GST_TYPE_RTSP_CLIENT_SINK_PAD (gst_rtsp_client_sink_pad_get_type ())
+ #define GST_RTSP_CLIENT_SINK_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTSP_CLIENT_SINK_PAD,GstRtspClientSinkPad))
+
+ static void
+ gst_rtsp_client_sink_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRtspClientSinkPad *pad;
+
+ pad = GST_RTSP_CLIENT_SINK_PAD (object);
+
+ switch (prop_id) {
+ case PROP_PAD_PAYLOADER:
+ GST_OBJECT_LOCK (pad);
+ if (pad->custom_payloader)
+ gst_object_unref (pad->custom_payloader);
+ pad->custom_payloader = g_value_get_object (value);
+ gst_object_ref_sink (pad->custom_payloader);
+ GST_OBJECT_UNLOCK (pad);
+ break;
+ case PROP_PAD_ULPFEC_PERCENTAGE:
+ GST_OBJECT_LOCK (pad);
+ pad->ulpfec_percentage = g_value_get_uint (value);
+ GST_OBJECT_UNLOCK (pad);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRtspClientSinkPad *pad;
+
+ pad = GST_RTSP_CLIENT_SINK_PAD (object);
+
+ switch (prop_id) {
+ case PROP_PAD_PAYLOADER:
+ GST_OBJECT_LOCK (pad);
+ g_value_set_object (value, pad->custom_payloader);
+ GST_OBJECT_UNLOCK (pad);
+ break;
+ case PROP_PAD_ULPFEC_PERCENTAGE:
+ GST_OBJECT_LOCK (pad);
+ g_value_set_uint (value, pad->ulpfec_percentage);
+ GST_OBJECT_UNLOCK (pad);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_pad_dispose (GObject * object)
+ {
+ GstRtspClientSinkPad *pad = GST_RTSP_CLIENT_SINK_PAD (object);
+
+ if (pad->custom_payloader)
+ gst_object_unref (pad->custom_payloader);
+
+ G_OBJECT_CLASS (gst_rtsp_client_sink_pad_parent_class)->dispose (object);
+ }
+
+ static void
+ gst_rtsp_client_sink_pad_class_init (GstRtspClientSinkPadClass * klass)
+ {
+ GObjectClass *gobject_klass;
+
+ gobject_klass = (GObjectClass *) klass;
+
+ gobject_klass->set_property = gst_rtsp_client_sink_pad_set_property;
+ gobject_klass->get_property = gst_rtsp_client_sink_pad_get_property;
+ gobject_klass->dispose = gst_rtsp_client_sink_pad_dispose;
+
+ g_object_class_install_property (gobject_klass, PROP_PAD_PAYLOADER,
+ g_param_spec_object ("payloader", "Payloader",
+ "The payloader element to use (NULL = default automatically selected)",
+ GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_klass, PROP_PAD_ULPFEC_PERCENTAGE,
+ g_param_spec_uint ("ulpfec-percentage", "ULPFEC percentage",
+ "The percentage of ULP redundancy to apply", 0, 100,
+ DEFAULT_PAD_ULPFEC_PERCENTAGE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ }
+
+ static void
+ gst_rtsp_client_sink_pad_init (GstRtspClientSinkPad * pad)
+ {
+ }
+
+ static GstPad *
+ gst_rtsp_client_sink_pad_new (const GstPadTemplate * pad_tmpl,
+ const gchar * name)
+ {
+ GstRtspClientSinkPad *ret;
+
+ ret =
+ g_object_new (GST_TYPE_RTSP_CLIENT_SINK_PAD, "direction", GST_PAD_SINK,
+ "template", pad_tmpl, "name", name, NULL);
+
+ return GST_PAD (ret);
+ }
+
+ GST_DEBUG_CATEGORY_STATIC (rtsp_client_sink_debug);
+ #define GST_CAT_DEFAULT (rtsp_client_sink_debug)
+
+ static GstStaticPadTemplate rtptemplate = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS_ANY); /* Actual caps come from available set of payloaders */
+
+ enum
+ {
+ SIGNAL_HANDLE_REQUEST,
+ SIGNAL_NEW_MANAGER,
+ SIGNAL_NEW_PAYLOADER,
+ SIGNAL_REQUEST_RTCP_KEY,
+ SIGNAL_ACCEPT_CERTIFICATE,
+ SIGNAL_UPDATE_SDP,
+ LAST_SIGNAL
+ };
+
+ enum _GstRTSPClientSinkNtpTimeSource
+ {
+ NTP_TIME_SOURCE_NTP,
+ NTP_TIME_SOURCE_UNIX,
+ NTP_TIME_SOURCE_RUNNING_TIME,
+ NTP_TIME_SOURCE_CLOCK_TIME
+ };
+
+ #define GST_TYPE_RTSP_CLIENT_SINK_NTP_TIME_SOURCE (gst_rtsp_client_sink_ntp_time_source_get_type())
+ static GType
+ gst_rtsp_client_sink_ntp_time_source_get_type (void)
+ {
+ static GType ntp_time_source_type = 0;
+ static const GEnumValue ntp_time_source_values[] = {
+ {NTP_TIME_SOURCE_NTP, "NTP time based on realtime clock", "ntp"},
+ {NTP_TIME_SOURCE_UNIX, "UNIX time based on realtime clock", "unix"},
+ {NTP_TIME_SOURCE_RUNNING_TIME,
+ "Running time based on pipeline clock",
+ "running-time"},
+ {NTP_TIME_SOURCE_CLOCK_TIME, "Pipeline clock time", "clock-time"},
+ {0, NULL, NULL},
+ };
+
+ if (!ntp_time_source_type) {
+ ntp_time_source_type =
+ g_enum_register_static ("GstRTSPClientSinkNtpTimeSource",
+ ntp_time_source_values);
+ }
+ return ntp_time_source_type;
+ }
+
+ #define DEFAULT_LOCATION NULL
+ #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP
+ #define DEFAULT_DEBUG FALSE
+ #define DEFAULT_RETRY 20
+ #define DEFAULT_TIMEOUT 5000000
+ #define DEFAULT_UDP_BUFFER_SIZE 0x80000
+ #define DEFAULT_TCP_TIMEOUT 20000000
+ #define DEFAULT_LATENCY_MS 2000
+ #define DEFAULT_DO_RTSP_KEEP_ALIVE TRUE
+ #define DEFAULT_PROXY NULL
+ #define DEFAULT_RTP_BLOCKSIZE 0
+ #define DEFAULT_USER_ID NULL
+ #define DEFAULT_USER_PW NULL
+ #define DEFAULT_PORT_RANGE NULL
+ #define DEFAULT_UDP_RECONNECT TRUE
+ #define DEFAULT_MULTICAST_IFACE NULL
+ #define DEFAULT_TLS_VALIDATION_FLAGS G_TLS_CERTIFICATE_VALIDATE_ALL
+ #define DEFAULT_TLS_DATABASE NULL
+ #define DEFAULT_TLS_INTERACTION NULL
+ #define DEFAULT_NTP_TIME_SOURCE NTP_TIME_SOURCE_NTP
+ #define DEFAULT_USER_AGENT "GStreamer/" PACKAGE_VERSION
+ #define DEFAULT_PROFILES GST_RTSP_PROFILE_AVP
+ #define DEFAULT_RTX_TIME_MS 500
+
+ enum
+ {
+ PROP_0,
+ PROP_LOCATION,
+ PROP_PROTOCOLS,
+ PROP_DEBUG,
+ PROP_RETRY,
+ PROP_TIMEOUT,
+ PROP_TCP_TIMEOUT,
+ PROP_LATENCY,
+ PROP_RTX_TIME,
+ PROP_DO_RTSP_KEEP_ALIVE,
+ PROP_PROXY,
+ PROP_PROXY_ID,
+ PROP_PROXY_PW,
+ PROP_RTP_BLOCKSIZE,
+ PROP_USER_ID,
+ PROP_USER_PW,
+ PROP_PORT_RANGE,
+ PROP_UDP_BUFFER_SIZE,
+ PROP_UDP_RECONNECT,
+ PROP_MULTICAST_IFACE,
+ PROP_SDES,
+ PROP_TLS_VALIDATION_FLAGS,
+ PROP_TLS_DATABASE,
+ PROP_TLS_INTERACTION,
+ PROP_NTP_TIME_SOURCE,
+ PROP_USER_AGENT,
+ PROP_PROFILES
+ };
+
+ static void gst_rtsp_client_sink_finalize (GObject * object);
+
+ static void gst_rtsp_client_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+ static void gst_rtsp_client_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+ static GstClock *gst_rtsp_client_sink_provide_clock (GstElement * element);
+
+ static void gst_rtsp_client_sink_uri_handler_init (gpointer g_iface,
+ gpointer iface_data);
+
+ static gboolean gst_rtsp_client_sink_set_proxy (GstRTSPClientSink * rtsp,
+ const gchar * proxy);
+ static void gst_rtsp_client_sink_set_tcp_timeout (GstRTSPClientSink *
+ rtsp_client_sink, guint64 timeout);
+
+ static GstStateChangeReturn gst_rtsp_client_sink_change_state (GstElement *
+ element, GstStateChange transition);
+ static void gst_rtsp_client_sink_handle_message (GstBin * bin,
+ GstMessage * message);
+
+ static gboolean gst_rtsp_client_sink_setup_auth (GstRTSPClientSink * sink,
+ GstRTSPMessage * response);
+
+ static gboolean gst_rtsp_client_sink_loop_send_cmd (GstRTSPClientSink * sink,
+ gint cmd, gint mask);
+
+ static GstRTSPResult gst_rtsp_client_sink_open (GstRTSPClientSink * sink,
+ gboolean async);
+ static GstRTSPResult gst_rtsp_client_sink_record (GstRTSPClientSink * sink,
+ gboolean async);
+ static GstRTSPResult gst_rtsp_client_sink_pause (GstRTSPClientSink * sink,
+ gboolean async);
+ static GstRTSPResult gst_rtsp_client_sink_close (GstRTSPClientSink * sink,
+ gboolean async, gboolean only_close);
+ static gboolean gst_rtsp_client_sink_collect_streams (GstRTSPClientSink * sink);
+
+ static gboolean gst_rtsp_client_sink_uri_set_uri (GstURIHandler * handler,
+ const gchar * uri, GError ** error);
+ static gchar *gst_rtsp_client_sink_uri_get_uri (GstURIHandler * handler);
+
+ static gboolean gst_rtsp_client_sink_loop (GstRTSPClientSink * sink);
+ static void gst_rtsp_client_sink_connection_flush (GstRTSPClientSink * sink,
+ gboolean flush);
+
+ static GstPad *gst_rtsp_client_sink_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
+ static void gst_rtsp_client_sink_release_pad (GstElement * element,
+ GstPad * pad);
+
+ /* commands we send to out loop to notify it of events */
+ #define CMD_OPEN (1 << 0)
+ #define CMD_RECORD (1 << 1)
+ #define CMD_PAUSE (1 << 2)
+ #define CMD_CLOSE (1 << 3)
+ #define CMD_WAIT (1 << 4)
+ #define CMD_RECONNECT (1 << 5)
+ #define CMD_LOOP (1 << 6)
+
+ /* mask for all commands */
+ #define CMD_ALL ((CMD_LOOP << 1) - 1)
+
+ #define GST_ELEMENT_PROGRESS(el, type, code, text) \
+ G_STMT_START { \
+ gchar *__txt = _gst_element_error_printf text; \
+ gst_element_post_message (GST_ELEMENT_CAST (el), \
+ gst_message_new_progress (GST_OBJECT_CAST (el), \
+ GST_PROGRESS_TYPE_ ##type, code, __txt)); \
+ g_free (__txt); \
+ } G_STMT_END
+
+ static guint gst_rtsp_client_sink_signals[LAST_SIGNAL] = { 0 };
+
+ /*********************************
+ * GstChildProxy implementation *
+ *********************************/
+ static GObject *
+ gst_rtsp_client_sink_child_proxy_get_child_by_index (GstChildProxy *
+ child_proxy, guint index)
+ {
+ GObject *obj;
+ GstRTSPClientSink *cs = GST_RTSP_CLIENT_SINK (child_proxy);
+
+ GST_OBJECT_LOCK (cs);
+ if ((obj = g_list_nth_data (GST_ELEMENT (cs)->sinkpads, index)))
+ g_object_ref (obj);
+ GST_OBJECT_UNLOCK (cs);
+
+ return obj;
+ }
+
+ static guint
+ gst_rtsp_client_sink_child_proxy_get_children_count (GstChildProxy *
+ child_proxy)
+ {
+ guint count = 0;
+
+ GST_OBJECT_LOCK (child_proxy);
+ count = GST_ELEMENT (child_proxy)->numsinkpads;
+ GST_OBJECT_UNLOCK (child_proxy);
+
+ GST_INFO_OBJECT (child_proxy, "Children Count: %d", count);
+
+ return count;
+ }
+
+ static void
+ gst_rtsp_client_sink_child_proxy_init (gpointer g_iface, gpointer iface_data)
+ {
+ GstChildProxyInterface *iface = g_iface;
+
+ GST_INFO ("intializing child proxy interface");
+ iface->get_child_by_index =
+ gst_rtsp_client_sink_child_proxy_get_child_by_index;
+ iface->get_children_count =
+ gst_rtsp_client_sink_child_proxy_get_children_count;
+ }
+
+ #define gst_rtsp_client_sink_parent_class parent_class
+ G_DEFINE_TYPE_WITH_CODE (GstRTSPClientSink, gst_rtsp_client_sink, GST_TYPE_BIN,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
+ gst_rtsp_client_sink_uri_handler_init);
+ G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
+ gst_rtsp_client_sink_child_proxy_init);
+ );
+
+ #ifndef GST_DISABLE_GST_DEBUG
+ static inline const gchar *
+ cmd_to_string (guint cmd)
+ {
+ switch (cmd) {
+ case CMD_OPEN:
+ return "OPEN";
+ case CMD_RECORD:
+ return "RECORD";
+ case CMD_PAUSE:
+ return "PAUSE";
+ case CMD_CLOSE:
+ return "CLOSE";
+ case CMD_WAIT:
+ return "WAIT";
+ case CMD_RECONNECT:
+ return "RECONNECT";
+ case CMD_LOOP:
+ return "LOOP";
+ }
+
+ return "unknown";
+ }
+ #endif
+
+ static void
+ gst_rtsp_client_sink_class_init (GstRTSPClientSinkClass * klass)
+ {
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBinClass *gstbin_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbin_class = (GstBinClass *) klass;
+
+ GST_DEBUG_CATEGORY_INIT (rtsp_client_sink_debug, "rtspclientsink", 0,
+ "RTSP sink element");
+
+ gobject_class->set_property = gst_rtsp_client_sink_set_property;
+ gobject_class->get_property = gst_rtsp_client_sink_get_property;
+
+ gobject_class->finalize = gst_rtsp_client_sink_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_LOCATION,
+ g_param_spec_string ("location", "RTSP Location",
+ "Location of the RTSP url to read",
+ DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PROTOCOLS,
+ g_param_spec_flags ("protocols", "Protocols",
+ "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS,
+ DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_PROFILES,
+ g_param_spec_flags ("profiles", "Profiles",
+ "Allowed RTSP profiles", GST_TYPE_RTSP_PROFILE,
+ DEFAULT_PROFILES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_DEBUG,
+ g_param_spec_boolean ("debug", "Debug",
+ "Dump request and response messages to stdout",
+ DEFAULT_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_RETRY,
+ g_param_spec_uint ("retry", "Retry",
+ "Max number of retries when allocating RTP ports.",
+ 0, G_MAXUINT16, DEFAULT_RETRY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TIMEOUT,
+ g_param_spec_uint64 ("timeout", "Timeout",
+ "Retry TCP transport after UDP timeout microseconds (0 = disabled)",
+ 0, G_MAXUINT64, DEFAULT_TIMEOUT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TCP_TIMEOUT,
+ g_param_spec_uint64 ("tcp-timeout", "TCP Timeout",
+ "Fail after timeout microseconds on TCP connections (0 = disabled)",
+ 0, G_MAXUINT64, DEFAULT_TCP_TIMEOUT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_LATENCY,
+ g_param_spec_uint ("latency", "Buffer latency in ms",
+ "Amount of ms to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_RTX_TIME,
+ g_param_spec_uint ("rtx-time", "Retransmission buffer in ms",
+ "Amount of ms to buffer for retransmission. 0 disables retransmission",
+ 0, G_MAXUINT, DEFAULT_RTX_TIME_MS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink:do-rtsp-keep-alive:
+ *
+ * Enable RTSP keep alive support. Some old server don't like RTSP
+ * keep alive and then this property needs to be set to FALSE.
+ */
+ g_object_class_install_property (gobject_class, PROP_DO_RTSP_KEEP_ALIVE,
+ g_param_spec_boolean ("do-rtsp-keep-alive", "Do RTSP Keep Alive",
+ "Send RTSP keep alive packets, disable for old incompatible server.",
+ DEFAULT_DO_RTSP_KEEP_ALIVE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink:proxy:
+ *
+ * Set the proxy parameters. This has to be a string of the format
+ * [http://][user:passwd@]host[:port].
+ */
+ g_object_class_install_property (gobject_class, PROP_PROXY,
+ g_param_spec_string ("proxy", "Proxy",
+ "Proxy settings for HTTP tunneling. Format: [http://][user:passwd@]host[:port]",
+ DEFAULT_PROXY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPClientSink:proxy-id:
+ *
+ * Sets the proxy URI user id for authentication. If the URI set via the
+ * "proxy" property contains a user-id already, that will take precedence.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_PROXY_ID,
+ g_param_spec_string ("proxy-id", "proxy-id",
+ "HTTP proxy URI user id for authentication", "",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * GstRTSPClientSink:proxy-pw:
+ *
+ * Sets the proxy URI password for authentication. If the URI set via the
+ * "proxy" property contains a password already, that will take precedence.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_PROXY_PW,
+ g_param_spec_string ("proxy-pw", "proxy-pw",
+ "HTTP proxy URI user password for authentication", "",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink:rtp-blocksize:
+ *
+ * RTP package size to suggest to server.
+ */
+ g_object_class_install_property (gobject_class, PROP_RTP_BLOCKSIZE,
+ g_param_spec_uint ("rtp-blocksize", "RTP Blocksize",
+ "RTP package size to suggest to server (0 = disabled)",
+ 0, 65536, DEFAULT_RTP_BLOCKSIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_USER_ID,
+ g_param_spec_string ("user-id", "user-id",
+ "RTSP location URI user id for authentication", DEFAULT_USER_ID,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_USER_PW,
+ g_param_spec_string ("user-pw", "user-pw",
+ "RTSP location URI user password for authentication", DEFAULT_USER_PW,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink:port-range:
+ *
+ * Configure the client port numbers that can be used to receive
+ * RTCP.
+ */
+ g_object_class_install_property (gobject_class, PROP_PORT_RANGE,
+ g_param_spec_string ("port-range", "Port range",
+ "Client port range that can be used to receive RTCP data, "
+ "eg. 3000-3005 (NULL = no restrictions)", DEFAULT_PORT_RANGE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink:udp-buffer-size:
+ *
+ * Size of the kernel UDP receive buffer in bytes.
+ */
+ g_object_class_install_property (gobject_class, PROP_UDP_BUFFER_SIZE,
+ g_param_spec_int ("udp-buffer-size", "UDP Buffer Size",
+ "Size of the kernel UDP receive buffer in bytes, 0=default",
+ 0, G_MAXINT, DEFAULT_UDP_BUFFER_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_UDP_RECONNECT,
+ g_param_spec_boolean ("udp-reconnect", "Reconnect to the server",
+ "Reconnect to the server if RTSP connection is closed when doing UDP",
+ DEFAULT_UDP_RECONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_MULTICAST_IFACE,
+ g_param_spec_string ("multicast-iface", "Multicast Interface",
+ "The network interface on which to join the multicast group",
+ DEFAULT_MULTICAST_IFACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SDES,
+ g_param_spec_boxed ("sdes", "SDES",
+ "The SDES items of this session",
+ GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink::tls-validation-flags:
+ *
+ * TLS certificate validation flags used to validate server
+ * certificate.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS,
+ g_param_spec_flags ("tls-validation-flags", "TLS validation flags",
+ "TLS certificate validation flags used to validate the server certificate",
+ G_TYPE_TLS_CERTIFICATE_FLAGS, DEFAULT_TLS_VALIDATION_FLAGS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink::tls-database:
+ *
+ * TLS database with anchor certificate authorities used to validate
+ * the server certificate.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_TLS_DATABASE,
+ g_param_spec_object ("tls-database", "TLS database",
+ "TLS database with anchor certificate authorities used to validate the server certificate",
+ G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink::tls-interaction:
+ *
+ * A #GTlsInteraction object to be used when the connection or certificate
+ * database need to interact with the user. This will be used to prompt the
+ * user for passwords where necessary.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION,
+ g_param_spec_object ("tls-interaction", "TLS interaction",
+ "A GTlsInteraction object to prompt the user for password or certificate",
+ G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink::ntp-time-source:
+ *
+ * allows to select the time source that should be used
+ * for the NTP time in outgoing packets
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_NTP_TIME_SOURCE,
+ g_param_spec_enum ("ntp-time-source", "NTP Time Source",
+ "NTP time source for RTCP packets",
+ GST_TYPE_RTSP_CLIENT_SINK_NTP_TIME_SOURCE, DEFAULT_NTP_TIME_SOURCE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink::user-agent:
+ *
+ * The string to set in the User-Agent header.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_USER_AGENT,
+ g_param_spec_string ("user-agent", "User Agent",
+ "The User-Agent string to send to the server",
+ DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstRTSPClientSink::handle-request:
+ * @rtsp_client_sink: a #GstRTSPClientSink
+ * @request: a #GstRTSPMessage
+ * @response: a #GstRTSPMessage
+ *
+ * Handle a server request in @request and prepare @response.
+ *
+ * This signal is called from the streaming thread, you should therefore not
+ * do any state changes on @rtsp_client_sink because this might deadlock. If you want
+ * to modify the state as a result of this signal, post a
+ * #GST_MESSAGE_REQUEST_STATE message on the bus or signal the main thread
+ * in some other way.
+ *
+ */
+ gst_rtsp_client_sink_signals[SIGNAL_HANDLE_REQUEST] =
+ g_signal_new ("handle-request", G_TYPE_FROM_CLASS (klass), 0,
+ 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
+ GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE,
+ GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GstRTSPClientSink::new-manager:
+ * @rtsp_client_sink: a #GstRTSPClientSink
+ * @manager: a #GstElement
+ *
+ * Emitted after a new manager (like rtpbin) was created and the default
+ * properties were configured.
+ *
+ */
+ gst_rtsp_client_sink_signals[SIGNAL_NEW_MANAGER] =
+ g_signal_new_class_handler ("new-manager", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ /**
+ * GstRTSPClientSink::new-payloader:
+ * @rtsp_client_sink: a #GstRTSPClientSink
+ * @payloader: a #GstElement
+ *
+ * Emitted after a new RTP payloader was created and the default
+ * properties were configured.
+ *
+ */
+ gst_rtsp_client_sink_signals[SIGNAL_NEW_PAYLOADER] =
+ g_signal_new_class_handler ("new-payloader", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+ /**
+ * GstRTSPClientSink::request-rtcp-key:
+ * @rtsp_client_sink: a #GstRTSPClientSink
+ * @num: the stream number
+ *
+ * Signal emitted to get the crypto parameters relevant to the RTCP
+ * stream. User should provide the key and the RTCP encryption ciphers
+ * and authentication, and return them wrapped in a GstCaps.
+ *
+ */
+ gst_rtsp_client_sink_signals[SIGNAL_REQUEST_RTCP_KEY] =
+ g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
+
+ /**
+ * GstRTSPClientSink::accept-certificate:
+ * @rtsp_client_sink: a #GstRTSPClientSink
+ * @peer_cert: the peer's #GTlsCertificate
+ * @errors: the problems with @peer_cert
+ * @user_data: user data set when the signal handler was connected.
+ *
+ * This will directly map to #GTlsConnection 's "accept-certificate"
+ * signal and be performed after the default checks of #GstRTSPConnection
+ * (checking against the #GTlsDatabase with the given #GTlsCertificateFlags)
+ * have failed. If no #GTlsDatabase is set on this connection, only this
+ * signal will be emitted.
+ *
+ * Since: 1.14
+ */
+ gst_rtsp_client_sink_signals[SIGNAL_ACCEPT_CERTIFICATE] =
+ g_signal_new ("accept-certificate", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 3, G_TYPE_TLS_CONNECTION, G_TYPE_TLS_CERTIFICATE,
+ G_TYPE_TLS_CERTIFICATE_FLAGS);
+
+ /**
+ * GstRTSPClientSink::update-sdp:
+ * @rtsp_client_sink: a #GstRTSPClientSink
+ * @sdp: a #GstSDPMessage
+ *
+ * Emitted right before the ANNOUNCE request is sent to the server with the
+ * generated SDP. The SDP can be updated from signal handlers but the order
+ * and number of medias must not be changed.
+ *
+ * Since: 1.20
+ */
+ gst_rtsp_client_sink_signals[SIGNAL_UPDATE_SDP] =
+ g_signal_new_class_handler ("update-sdp", G_TYPE_FROM_CLASS (klass),
+ 0, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GST_TYPE_SDP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ gstelement_class->provide_clock = gst_rtsp_client_sink_provide_clock;
+ gstelement_class->change_state = gst_rtsp_client_sink_change_state;
+ gstelement_class->request_new_pad =
+ GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_request_new_pad);
+ gstelement_class->release_pad =
+ GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_release_pad);
+
+ gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
+ &rtptemplate, GST_TYPE_RTSP_CLIENT_SINK_PAD);
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "RTSP RECORD client", "Sink/Network",
+ "Send data over the network via RTSP RECORD(RFC 2326)",
+ "Jan Schmidt <jan@centricular.com>");
+
+ gstbin_class->handle_message = gst_rtsp_client_sink_handle_message;
+
+ gst_type_mark_as_plugin_api (GST_TYPE_RTSP_CLIENT_SINK_PAD, 0);
+ gst_type_mark_as_plugin_api (GST_TYPE_RTSP_CLIENT_SINK_NTP_TIME_SOURCE, 0);
+ }
+
+ static void
+ gst_rtsp_client_sink_init (GstRTSPClientSink * sink)
+ {
+ sink->conninfo.location = g_strdup (DEFAULT_LOCATION);
+ sink->protocols = DEFAULT_PROTOCOLS;
+ sink->debug = DEFAULT_DEBUG;
+ sink->retry = DEFAULT_RETRY;
+ sink->udp_timeout = DEFAULT_TIMEOUT;
+ gst_rtsp_client_sink_set_tcp_timeout (sink, DEFAULT_TCP_TIMEOUT);
+ sink->latency = DEFAULT_LATENCY_MS;
+ sink->rtx_time = DEFAULT_RTX_TIME_MS;
+ sink->do_rtsp_keep_alive = DEFAULT_DO_RTSP_KEEP_ALIVE;
+ gst_rtsp_client_sink_set_proxy (sink, DEFAULT_PROXY);
+ sink->rtp_blocksize = DEFAULT_RTP_BLOCKSIZE;
+ sink->user_id = g_strdup (DEFAULT_USER_ID);
+ sink->user_pw = g_strdup (DEFAULT_USER_PW);
+ sink->client_port_range.min = 0;
+ sink->client_port_range.max = 0;
+ sink->udp_buffer_size = DEFAULT_UDP_BUFFER_SIZE;
+ sink->udp_reconnect = DEFAULT_UDP_RECONNECT;
+ sink->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE);
+ sink->sdes = NULL;
+ sink->tls_validation_flags = DEFAULT_TLS_VALIDATION_FLAGS;
+ sink->tls_database = DEFAULT_TLS_DATABASE;
+ sink->tls_interaction = DEFAULT_TLS_INTERACTION;
+ sink->ntp_time_source = DEFAULT_NTP_TIME_SOURCE;
+ sink->user_agent = g_strdup (DEFAULT_USER_AGENT);
+
+ sink->profiles = DEFAULT_PROFILES;
+
+ /* protects the streaming thread in interleaved mode or the polling
+ * thread in UDP mode. */
+ g_rec_mutex_init (&sink->stream_rec_lock);
+
+ /* protects our state changes from multiple invocations */
+ g_rec_mutex_init (&sink->state_rec_lock);
+
+ g_mutex_init (&sink->send_lock);
+
+ g_mutex_init (&sink->preroll_lock);
+ g_cond_init (&sink->preroll_cond);
+
+ sink->state = GST_RTSP_STATE_INVALID;
+
+ g_mutex_init (&sink->conninfo.send_lock);
+ g_mutex_init (&sink->conninfo.recv_lock);
+
+ g_mutex_init (&sink->block_streams_lock);
+ g_cond_init (&sink->block_streams_cond);
+
+ g_mutex_init (&sink->open_conn_lock);
+ g_cond_init (&sink->open_conn_cond);
+
+ sink->internal_bin = (GstBin *) gst_bin_new ("rtspbin");
+ g_object_set (sink->internal_bin, "async-handling", TRUE, NULL);
+ gst_element_set_locked_state (GST_ELEMENT_CAST (sink->internal_bin), TRUE);
+ gst_bin_add (GST_BIN (sink), GST_ELEMENT_CAST (sink->internal_bin));
+
+ sink->next_dyn_pt = 96;
+
+ gst_sdp_message_init (&sink->cursdp);
+
+ GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
+ }
+
+ static void
+ gst_rtsp_client_sink_finalize (GObject * object)
+ {
+ GstRTSPClientSink *rtsp_client_sink;
+
+ rtsp_client_sink = GST_RTSP_CLIENT_SINK (object);
+
+ gst_sdp_message_uninit (&rtsp_client_sink->cursdp);
+
+ g_free (rtsp_client_sink->conninfo.location);
+ gst_rtsp_url_free (rtsp_client_sink->conninfo.url);
+ g_free (rtsp_client_sink->conninfo.url_str);
+ g_free (rtsp_client_sink->user_id);
+ g_free (rtsp_client_sink->user_pw);
+ g_free (rtsp_client_sink->multi_iface);
+ g_free (rtsp_client_sink->user_agent);
+
+ if (rtsp_client_sink->uri_sdp) {
+ gst_sdp_message_free (rtsp_client_sink->uri_sdp);
+ rtsp_client_sink->uri_sdp = NULL;
+ }
+ if (rtsp_client_sink->provided_clock)
+ gst_object_unref (rtsp_client_sink->provided_clock);
+
+ if (rtsp_client_sink->sdes)
+ gst_structure_free (rtsp_client_sink->sdes);
+
+ if (rtsp_client_sink->tls_database)
+ g_object_unref (rtsp_client_sink->tls_database);
+
+ if (rtsp_client_sink->tls_interaction)
+ g_object_unref (rtsp_client_sink->tls_interaction);
+
+ /* free locks */
+ g_rec_mutex_clear (&rtsp_client_sink->stream_rec_lock);
+ g_rec_mutex_clear (&rtsp_client_sink->state_rec_lock);
+
+ g_mutex_clear (&rtsp_client_sink->conninfo.send_lock);
+ g_mutex_clear (&rtsp_client_sink->conninfo.recv_lock);
+
+ g_mutex_clear (&rtsp_client_sink->send_lock);
+
+ g_mutex_clear (&rtsp_client_sink->preroll_lock);
+ g_cond_clear (&rtsp_client_sink->preroll_cond);
+
+ g_mutex_clear (&rtsp_client_sink->block_streams_lock);
+ g_cond_clear (&rtsp_client_sink->block_streams_cond);
+
+ g_mutex_clear (&rtsp_client_sink->open_conn_lock);
+ g_cond_clear (&rtsp_client_sink->open_conn_cond);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+ }
+
+ static gboolean
+ gst_rtp_payloader_filter_func (GstPluginFeature * feature, gpointer user_data)
+ {
+ GstElementFactory *factory = NULL;
+ const gchar *klass;
+
+ if (!GST_IS_ELEMENT_FACTORY (feature))
+ return FALSE;
+
+ factory = GST_ELEMENT_FACTORY (feature);
+
+ if (gst_plugin_feature_get_rank (feature) == GST_RANK_NONE)
+ return FALSE;
+
+ if (!gst_element_factory_list_is_type (factory,
+ GST_ELEMENT_FACTORY_TYPE_PAYLOADER))
+ return FALSE;
+
+ klass =
+ gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
+ if (strstr (klass, "Codec") == NULL)
+ return FALSE;
+ if (strstr (klass, "RTP") == NULL)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ static gint
+ compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
+ {
+ gint diff;
+ const gchar *rname1, *rname2;
+ GstRank rank1, rank2;
+
+ rname1 = gst_plugin_feature_get_name (f1);
+ rname2 = gst_plugin_feature_get_name (f2);
+
+ rank1 = gst_plugin_feature_get_rank (f1);
+ rank2 = gst_plugin_feature_get_rank (f2);
+
+ /* HACK: Prefer rtpmp4apay over rtpmp4gpay */
+ if (g_str_equal (rname1, "rtpmp4apay"))
+ rank1 = GST_RANK_SECONDARY + 1;
+ if (g_str_equal (rname2, "rtpmp4apay"))
+ rank2 = GST_RANK_SECONDARY + 1;
+
+ diff = rank2 - rank1;
+ if (diff != 0)
+ return diff;
+
+ diff = strcmp (rname2, rname1);
+
+ return diff;
+ }
+
+ static GList *
+ gst_rtsp_client_sink_get_factories (void)
+ {
+ static GList *payloader_factories = NULL;
+
+ if (g_once_init_enter (&payloader_factories)) {
+ GList *all_factories;
+
+ all_factories =
+ gst_registry_feature_filter (gst_registry_get (),
+ gst_rtp_payloader_filter_func, FALSE, NULL);
+
+ all_factories = g_list_sort (all_factories, (GCompareFunc) compare_ranks);
+
+ g_once_init_leave (&payloader_factories, all_factories);
+ }
+
+ return payloader_factories;
+ }
+
+ static GstCaps *
+ gst_rtsp_client_sink_get_payloader_caps (GstElementFactory * factory)
+ {
+ const GList *tmp;
+ GstCaps *caps = gst_caps_new_empty ();
+
+ for (tmp = gst_element_factory_get_static_pad_templates (factory);
+ tmp; tmp = g_list_next (tmp)) {
+ GstStaticPadTemplate *template = tmp->data;
+
+ if (template->direction == GST_PAD_SINK) {
+ GstCaps *static_caps = gst_static_pad_template_get_caps (template);
+
+ GST_LOG ("Found pad template %s on factory %s",
+ template->name_template, gst_plugin_feature_get_name (factory));
+
+ if (static_caps)
+ caps = gst_caps_merge (caps, static_caps);
+
+ /* Early out, any is absorbing */
+ if (gst_caps_is_any (caps))
+ goto out;
+ }
+ }
+
+ out:
+ return caps;
+ }
+
+ static GstCaps *
+ gst_rtsp_client_sink_get_all_payloaders_caps (void)
+ {
+ /* Cached caps result */
+ static GstCaps *ret;
+
+ if (g_once_init_enter (&ret)) {
+ GList *factories, *cur;
+ GstCaps *caps = gst_caps_new_empty ();
+
+ factories = gst_rtsp_client_sink_get_factories ();
+ for (cur = factories; cur != NULL; cur = g_list_next (cur)) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY (cur->data);
+ GstCaps *payloader_caps =
+ gst_rtsp_client_sink_get_payloader_caps (factory);
+
+ caps = gst_caps_merge (caps, payloader_caps);
+
+ /* Early out, any is absorbing */
+ if (gst_caps_is_any (caps))
+ goto out;
+ }
+
+ GST_MINI_OBJECT_FLAG_SET (caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
+
+ out:
+ g_once_init_leave (&ret, caps);
+ }
+
+ /* Return cached result */
+ return gst_caps_ref (ret);
+ }
+
+ static GstElement *
+ gst_rtsp_client_sink_make_payloader (GstCaps * caps)
+ {
+ GList *factories, *cur;
+
+ factories = gst_rtsp_client_sink_get_factories ();
+ for (cur = factories; cur != NULL; cur = g_list_next (cur)) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY (cur->data);
+ const GList *tmp;
+
+ for (tmp = gst_element_factory_get_static_pad_templates (factory);
+ tmp; tmp = g_list_next (tmp)) {
+ GstStaticPadTemplate *template = tmp->data;
+
+ if (template->direction == GST_PAD_SINK) {
+ GstCaps *static_caps = gst_static_pad_template_get_caps (template);
+ GstElement *payloader = NULL;
+
+ if (gst_caps_can_intersect (static_caps, caps)) {
+ GST_DEBUG ("caps %" GST_PTR_FORMAT " intersects with template %"
+ GST_PTR_FORMAT " for payloader %s", caps, static_caps,
+ gst_plugin_feature_get_name (factory));
+ payloader = gst_element_factory_create (factory, NULL);
+ }
+
+ gst_caps_unref (static_caps);
+
+ if (payloader)
+ return payloader;
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ static GstRTSPStream *
+ gst_rtsp_client_sink_create_stream (GstRTSPClientSink * sink,
+ GstRTSPStreamContext * context, GstElement * payloader, GstPad * pad)
+ {
+ GstRTSPStream *stream = NULL;
+ guint pt, aux_pt, ulpfec_pt;
+
+ GST_OBJECT_LOCK (sink);
+
+ g_object_get (G_OBJECT (payloader), "pt", &pt, NULL);
+ if (pt >= 96 && pt <= sink->next_dyn_pt) {
+ /* Payloader has a dynamic PT, but one that's already used */
+ /* FIXME: Create a caps->ptmap instead? */
+ pt = sink->next_dyn_pt;
+
+ if (pt > 127)
+ goto no_free_pt;
+
+ GST_DEBUG_OBJECT (sink, "Assigning pt %u to stream %d", pt, context->index);
+
+ sink->next_dyn_pt++;
+ } else {
+ GST_DEBUG_OBJECT (sink, "Keeping existing pt %u for stream %d",
+ pt, context->index);
+ }
+
+ aux_pt = sink->next_dyn_pt;
+ if (aux_pt > 127)
+ goto no_free_pt;
+ sink->next_dyn_pt++;
+
+ ulpfec_pt = sink->next_dyn_pt;
+ if (ulpfec_pt > 127)
+ goto no_free_pt;
+ sink->next_dyn_pt++;
+
+ GST_OBJECT_UNLOCK (sink);
+
+
+ g_object_set (G_OBJECT (payloader), "pt", pt, NULL);
+
+ stream = gst_rtsp_stream_new (context->index, payloader, pad);
+
+ gst_rtsp_stream_set_client_side (stream, TRUE);
+ gst_rtsp_stream_set_retransmission_time (stream,
+ (GstClockTime) (sink->rtx_time) * GST_MSECOND);
+ gst_rtsp_stream_set_protocols (stream, sink->protocols);
+ gst_rtsp_stream_set_profiles (stream, sink->profiles);
+ gst_rtsp_stream_set_retransmission_pt (stream, aux_pt);
+ gst_rtsp_stream_set_buffer_size (stream, sink->udp_buffer_size);
+ if (sink->rtp_blocksize > 0)
+ gst_rtsp_stream_set_mtu (stream, sink->rtp_blocksize);
+ gst_rtsp_stream_set_multicast_iface (stream, sink->multi_iface);
+
+ gst_rtsp_stream_set_ulpfec_pt (stream, ulpfec_pt);
+ gst_rtsp_stream_set_ulpfec_percentage (stream, context->ulpfec_percentage);
+
+ #if 0
+ if (priv->pool)
+ gst_rtsp_stream_set_address_pool (stream, priv->pool);
+ #endif
+
+ return stream;
+ no_free_pt:
+ GST_OBJECT_UNLOCK (sink);
+
+ GST_ELEMENT_ERROR (sink, RESOURCE, NO_SPACE_LEFT, (NULL),
+ ("Ran out of dynamic payload types."));
+
+ return NULL;
+ }
+
+ static GstPadProbeReturn
+ handle_payloader_block (GstPad * pad, GstPadProbeInfo * info,
+ GstRTSPStreamContext * context)
+ {
+ GstRTSPClientSink *sink = context->parent;
+
+ GST_INFO_OBJECT (sink, "Block on pad %" GST_PTR_FORMAT, pad);
+
+ g_mutex_lock (&sink->preroll_lock);
+ context->prerolled = TRUE;
+ g_cond_broadcast (&sink->preroll_cond);
+ g_mutex_unlock (&sink->preroll_lock);
+
+ GST_INFO_OBJECT (sink, "Announced preroll on pad %" GST_PTR_FORMAT, pad);
+
+ return GST_PAD_PROBE_OK;
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_setup_payloader (GstRTSPClientSink * sink, GstPad * pad,
+ GstCaps * caps)
+ {
+ GstRTSPStreamContext *context;
+ GstRtspClientSinkPad *cspad = GST_RTSP_CLIENT_SINK_PAD (pad);
+
+ GstElement *payloader;
+ GstPad *sinkpad, *srcpad, *ghostsink;
+
+ context = gst_pad_get_element_private (pad);
+
+ if (cspad->custom_payloader) {
+ payloader = cspad->custom_payloader;
+ } else {
+ /* Find the payloader. */
+ payloader = gst_rtsp_client_sink_make_payloader (caps);
+ }
+
+ if (payloader == NULL)
+ return FALSE;
+
+ GST_DEBUG_OBJECT (sink, "Configuring payloader %" GST_PTR_FORMAT
+ " for pad %" GST_PTR_FORMAT, payloader, pad);
+
+ sinkpad = gst_element_get_static_pad (payloader, "sink");
+ if (sinkpad == NULL)
+ goto no_sinkpad;
+
+ srcpad = gst_element_get_static_pad (payloader, "src");
+ if (srcpad == NULL)
+ goto no_srcpad;
+
+ gst_bin_add (GST_BIN (sink->internal_bin), payloader);
+ ghostsink = gst_ghost_pad_new (NULL, sinkpad);
+ gst_pad_set_active (ghostsink, TRUE);
+ gst_element_add_pad (GST_ELEMENT (sink->internal_bin), ghostsink);
+
+ g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_NEW_PAYLOADER], 0,
+ payloader);
+
+ GST_RTSP_STATE_LOCK (sink);
+ context->payloader_block_id =
+ gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+ (GstPadProbeCallback) handle_payloader_block, context, NULL);
+ context->payloader = payloader;
+
+ payloader = gst_object_ref (payloader);
+
+ gst_ghost_pad_set_target (GST_GHOST_PAD (pad), ghostsink);
+ gst_object_unref (GST_OBJECT (sinkpad));
+ GST_RTSP_STATE_UNLOCK (sink);
+
+ context->ulpfec_percentage = cspad->ulpfec_percentage;
+
+ gst_element_sync_state_with_parent (payloader);
+
+ gst_object_unref (payloader);
+ gst_object_unref (GST_OBJECT (srcpad));
+
+ return TRUE;
+
+ no_sinkpad:
+ GST_ERROR_OBJECT (sink,
+ "Could not find sink pad on payloader %" GST_PTR_FORMAT, payloader);
+ if (!cspad->custom_payloader)
+ gst_object_unref (payloader);
+ return FALSE;
+
+ no_srcpad:
+ GST_ERROR_OBJECT (sink,
+ "Could not find src pad on payloader %" GST_PTR_FORMAT, payloader);
+ gst_object_unref (GST_OBJECT (sinkpad));
+ gst_object_unref (payloader);
+ return TRUE;
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_sinkpad_event (GstPad * pad, GstObject * parent,
+ GstEvent * event)
+ {
+ if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
+ GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
+ if (target == NULL) {
+ GstCaps *caps;
+
+ /* No target yet - choose a payloader and configure it */
+ gst_event_parse_caps (event, &caps);
+
+ GST_DEBUG_OBJECT (parent,
+ "Have set caps event on pad %" GST_PTR_FORMAT
+ " caps %" GST_PTR_FORMAT, pad, caps);
+
+ if (!gst_rtsp_client_sink_setup_payloader (GST_RTSP_CLIENT_SINK (parent),
+ pad, caps)) {
+ GstRtspClientSinkPad *cspad = GST_RTSP_CLIENT_SINK_PAD (pad);
+ GST_ELEMENT_ERROR (parent, CORE, NEGOTIATION,
+ ("Could not create payloader"),
+ ("Custom payloader: %p, caps: %" GST_PTR_FORMAT,
+ cspad->custom_payloader, caps));
+ gst_event_unref (event);
+ return FALSE;
+ }
+ } else {
+ gst_object_unref (target);
+ }
+ }
+
+ return gst_pad_event_default (pad, parent, event);
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_sinkpad_query (GstPad * pad, GstObject * parent,
+ GstQuery * query)
+ {
+ if (GST_QUERY_TYPE (query) == GST_QUERY_CAPS) {
+ GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
+ if (target == NULL) {
+ GstRtspClientSinkPad *cspad = GST_RTSP_CLIENT_SINK_PAD (pad);
+ GstCaps *caps;
+
+ if (cspad->custom_payloader) {
+ GstPad *sinkpad =
+ gst_element_get_static_pad (cspad->custom_payloader, "sink");
+
+ if (sinkpad) {
+ caps = gst_pad_query_caps (sinkpad, NULL);
+ gst_object_unref (sinkpad);
+ } else {
+ GST_ELEMENT_ERROR (parent, CORE, NEGOTIATION, (NULL),
+ ("Custom payloaders are expected to expose a sink pad named 'sink'"));
+ return FALSE;
+ }
+ } else {
+ /* No target yet - return the union of all payloader caps */
+ caps = gst_rtsp_client_sink_get_all_payloaders_caps ();
+ }
+
+ GST_TRACE_OBJECT (parent, "Returning payloader caps %" GST_PTR_FORMAT,
+ caps);
+
+ gst_query_set_caps_result (query, caps);
+ gst_caps_unref (caps);
+
+ return TRUE;
+ }
+ gst_object_unref (target);
+ }
+
+ return gst_pad_query_default (pad, parent, query);
+ }
+
+ static GstPad *
+ gst_rtsp_client_sink_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
+ {
+ GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (element);
+ GstPad *pad;
+ GstRTSPStreamContext *context;
+ guint idx = (guint) - 1;
+ gchar *tmpname;
+
+ g_mutex_lock (&sink->preroll_lock);
+ if (sink->streams_collected) {
+ GST_WARNING_OBJECT (element, "Can't add streams to a running session");
+ g_mutex_unlock (&sink->preroll_lock);
+ return NULL;
+ }
+ g_mutex_unlock (&sink->preroll_lock);
+
+ GST_OBJECT_LOCK (sink);
+ if (name) {
+ if (!sscanf (name, "sink_%u", &idx)) {
+ GST_OBJECT_UNLOCK (sink);
+ GST_ERROR_OBJECT (element, "Invalid sink pad name %s", name);
+ return NULL;
+ }
+
+ if (idx >= sink->next_pad_id)
+ sink->next_pad_id = idx + 1;
+ }
+ if (idx == (guint) - 1) {
+ idx = sink->next_pad_id;
+ sink->next_pad_id++;
+ }
+ GST_OBJECT_UNLOCK (sink);
+
+ tmpname = g_strdup_printf ("sink_%u", idx);
+ pad = gst_rtsp_client_sink_pad_new (templ, tmpname);
+ g_free (tmpname);
+
+ GST_DEBUG_OBJECT (element, "Creating request pad %" GST_PTR_FORMAT, pad);
+
+ gst_pad_set_event_function (pad,
+ GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_sinkpad_event));
+ gst_pad_set_query_function (pad,
+ GST_DEBUG_FUNCPTR (gst_rtsp_client_sink_sinkpad_query));
+
+ context = g_new0 (GstRTSPStreamContext, 1);
+ context->parent = sink;
+ context->index = idx;
+
+ gst_pad_set_element_private (pad, context);
+
+ /* The rest of the context is configured on a caps set */
+ gst_pad_set_active (pad, TRUE);
+ gst_element_add_pad (element, pad);
+ gst_child_proxy_child_added (GST_CHILD_PROXY (element), G_OBJECT (pad),
+ GST_PAD_NAME (pad));
+
+ (void) gst_rtsp_client_sink_get_factories ();
+
+ g_mutex_init (&context->conninfo.send_lock);
+ g_mutex_init (&context->conninfo.recv_lock);
+
+ GST_RTSP_STATE_LOCK (sink);
+ sink->contexts = g_list_prepend (sink->contexts, context);
+ GST_RTSP_STATE_UNLOCK (sink);
+
+ return pad;
+ }
+
+ static void
+ gst_rtsp_client_sink_release_pad (GstElement * element, GstPad * pad)
+ {
+ GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (element);
+ GstRTSPStreamContext *context;
+
+ context = gst_pad_get_element_private (pad);
+
+ /* FIXME: we may need to change our blocking state waiting for
+ * GstRTSPStreamBlocking messages */
+
+ GST_RTSP_STATE_LOCK (sink);
+ sink->contexts = g_list_remove (sink->contexts, context);
+ GST_RTSP_STATE_UNLOCK (sink);
+
+ /* FIXME: Shut down and clean up streaming on this pad,
+ * do teardown if needed */
+ GST_LOG_OBJECT (sink,
+ "Cleaning up payloader and stream for released pad %" GST_PTR_FORMAT,
+ pad);
+
+ if (context->stream_transport) {
+ gst_rtsp_stream_transport_set_active (context->stream_transport, FALSE);
+ gst_object_unref (context->stream_transport);
+ context->stream_transport = NULL;
+ }
+ if (context->stream) {
+ if (context->joined) {
+ gst_rtsp_stream_leave_bin (context->stream,
+ GST_BIN (sink->internal_bin), sink->rtpbin);
+ context->joined = FALSE;
+ }
+ gst_object_unref (context->stream);
+ context->stream = NULL;
+ }
+ if (context->srtcpparams)
+ gst_caps_unref (context->srtcpparams);
+
+ g_free (context->conninfo.location);
+ context->conninfo.location = NULL;
+
+ g_mutex_clear (&context->conninfo.send_lock);
+ g_mutex_clear (&context->conninfo.recv_lock);
+
+ g_free (context);
+
+ gst_element_remove_pad (element, pad);
+ }
+
+ static GstClock *
+ gst_rtsp_client_sink_provide_clock (GstElement * element)
+ {
+ GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (element);
+ GstClock *clock;
+
+ if ((clock = sink->provided_clock) != NULL)
+ gst_object_ref (clock);
+
+ return clock;
+ }
+
+ /* a proxy string of the format [user:passwd@]host[:port] */
+ static gboolean
+ gst_rtsp_client_sink_set_proxy (GstRTSPClientSink * rtsp, const gchar * proxy)
+ {
+ gchar *p, *at, *col;
+
+ g_free (rtsp->proxy_user);
+ rtsp->proxy_user = NULL;
+ g_free (rtsp->proxy_passwd);
+ rtsp->proxy_passwd = NULL;
+ g_free (rtsp->proxy_host);
+ rtsp->proxy_host = NULL;
+ rtsp->proxy_port = 0;
+
+ p = (gchar *) proxy;
+
+ if (p == NULL)
+ return TRUE;
+
+ /* we allow http:// in front but ignore it */
+ if (g_str_has_prefix (p, "http://"))
+ p += 7;
+
+ at = strchr (p, '@');
+ if (at) {
+ /* look for user:passwd */
+ col = strchr (proxy, ':');
+ if (col == NULL || col > at)
+ return FALSE;
+
+ rtsp->proxy_user = g_strndup (p, col - p);
+ col++;
+ rtsp->proxy_passwd = g_strndup (col, at - col);
+
+ /* move to host */
+ p = at + 1;
+ } else {
+ if (rtsp->prop_proxy_id != NULL && *rtsp->prop_proxy_id != '\0')
+ rtsp->proxy_user = g_strdup (rtsp->prop_proxy_id);
+ if (rtsp->prop_proxy_pw != NULL && *rtsp->prop_proxy_pw != '\0')
+ rtsp->proxy_passwd = g_strdup (rtsp->prop_proxy_pw);
+ if (rtsp->proxy_user != NULL || rtsp->proxy_passwd != NULL) {
+ GST_LOG_OBJECT (rtsp, "set proxy user/pw from properties: %s:%s",
+ GST_STR_NULL (rtsp->proxy_user), GST_STR_NULL (rtsp->proxy_passwd));
+ }
+ }
+ col = strchr (p, ':');
+
+ if (col) {
+ /* everything before the colon is the hostname */
+ rtsp->proxy_host = g_strndup (p, col - p);
+ p = col + 1;
+ rtsp->proxy_port = strtoul (p, (char **) &p, 10);
+ } else {
+ rtsp->proxy_host = g_strdup (p);
+ rtsp->proxy_port = 8080;
+ }
+ return TRUE;
+ }
+
+ static void
+ gst_rtsp_client_sink_set_tcp_timeout (GstRTSPClientSink * rtsp_client_sink,
+ guint64 timeout)
+ {
+ rtsp_client_sink->tcp_timeout = timeout;
+ }
+
+ static void
+ gst_rtsp_client_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPClientSink *rtsp_client_sink;
+
+ rtsp_client_sink = GST_RTSP_CLIENT_SINK (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ gst_rtsp_client_sink_uri_set_uri (GST_URI_HANDLER (rtsp_client_sink),
+ g_value_get_string (value), NULL);
+ break;
+ case PROP_PROTOCOLS:
+ rtsp_client_sink->protocols = g_value_get_flags (value);
+ break;
+ case PROP_PROFILES:
+ rtsp_client_sink->profiles = g_value_get_flags (value);
+ break;
+ case PROP_DEBUG:
+ rtsp_client_sink->debug = g_value_get_boolean (value);
+ break;
+ case PROP_RETRY:
+ rtsp_client_sink->retry = g_value_get_uint (value);
+ break;
+ case PROP_TIMEOUT:
+ rtsp_client_sink->udp_timeout = g_value_get_uint64 (value);
+ break;
+ case PROP_TCP_TIMEOUT:
+ gst_rtsp_client_sink_set_tcp_timeout (rtsp_client_sink,
+ g_value_get_uint64 (value));
+ break;
+ case PROP_LATENCY:
+ rtsp_client_sink->latency = g_value_get_uint (value);
+ break;
+ case PROP_RTX_TIME:
+ rtsp_client_sink->rtx_time = g_value_get_uint (value);
+ break;
+ case PROP_DO_RTSP_KEEP_ALIVE:
+ rtsp_client_sink->do_rtsp_keep_alive = g_value_get_boolean (value);
+ break;
+ case PROP_PROXY:
+ gst_rtsp_client_sink_set_proxy (rtsp_client_sink,
+ g_value_get_string (value));
+ break;
+ case PROP_PROXY_ID:
+ if (rtsp_client_sink->prop_proxy_id)
+ g_free (rtsp_client_sink->prop_proxy_id);
+ rtsp_client_sink->prop_proxy_id = g_value_dup_string (value);
+ break;
+ case PROP_PROXY_PW:
+ if (rtsp_client_sink->prop_proxy_pw)
+ g_free (rtsp_client_sink->prop_proxy_pw);
+ rtsp_client_sink->prop_proxy_pw = g_value_dup_string (value);
+ break;
+ case PROP_RTP_BLOCKSIZE:
+ rtsp_client_sink->rtp_blocksize = g_value_get_uint (value);
+ break;
+ case PROP_USER_ID:
+ if (rtsp_client_sink->user_id)
+ g_free (rtsp_client_sink->user_id);
+ rtsp_client_sink->user_id = g_value_dup_string (value);
+ break;
+ case PROP_USER_PW:
+ if (rtsp_client_sink->user_pw)
+ g_free (rtsp_client_sink->user_pw);
+ rtsp_client_sink->user_pw = g_value_dup_string (value);
+ break;
+ case PROP_PORT_RANGE:
+ {
+ const gchar *str;
+
+ str = g_value_get_string (value);
+ if (!str || !sscanf (str, "%u-%u",
+ &rtsp_client_sink->client_port_range.min,
+ &rtsp_client_sink->client_port_range.max)) {
+ rtsp_client_sink->client_port_range.min = 0;
+ rtsp_client_sink->client_port_range.max = 0;
+ }
+ break;
+ }
+ case PROP_UDP_BUFFER_SIZE:
+ rtsp_client_sink->udp_buffer_size = g_value_get_int (value);
+ break;
+ case PROP_UDP_RECONNECT:
+ rtsp_client_sink->udp_reconnect = g_value_get_boolean (value);
+ break;
+ case PROP_MULTICAST_IFACE:
+ g_free (rtsp_client_sink->multi_iface);
+
+ if (g_value_get_string (value) == NULL)
+ rtsp_client_sink->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE);
+ else
+ rtsp_client_sink->multi_iface = g_value_dup_string (value);
+ break;
+ case PROP_SDES:
+ rtsp_client_sink->sdes = g_value_dup_boxed (value);
+ break;
+ case PROP_TLS_VALIDATION_FLAGS:
+ rtsp_client_sink->tls_validation_flags = g_value_get_flags (value);
+ break;
+ case PROP_TLS_DATABASE:
+ g_clear_object (&rtsp_client_sink->tls_database);
+ rtsp_client_sink->tls_database = g_value_dup_object (value);
+ break;
+ case PROP_TLS_INTERACTION:
+ g_clear_object (&rtsp_client_sink->tls_interaction);
+ rtsp_client_sink->tls_interaction = g_value_dup_object (value);
+ break;
+ case PROP_NTP_TIME_SOURCE:
+ rtsp_client_sink->ntp_time_source = g_value_get_enum (value);
+ break;
+ case PROP_USER_AGENT:
+ g_free (rtsp_client_sink->user_agent);
+ rtsp_client_sink->user_agent = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+ {
+ GstRTSPClientSink *rtsp_client_sink;
+
+ rtsp_client_sink = GST_RTSP_CLIENT_SINK (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ g_value_set_string (value, rtsp_client_sink->conninfo.location);
+ break;
+ case PROP_PROTOCOLS:
+ g_value_set_flags (value, rtsp_client_sink->protocols);
+ break;
+ case PROP_PROFILES:
+ g_value_set_flags (value, rtsp_client_sink->profiles);
+ break;
+ case PROP_DEBUG:
+ g_value_set_boolean (value, rtsp_client_sink->debug);
+ break;
+ case PROP_RETRY:
+ g_value_set_uint (value, rtsp_client_sink->retry);
+ break;
+ case PROP_TIMEOUT:
+ g_value_set_uint64 (value, rtsp_client_sink->udp_timeout);
+ break;
+ case PROP_TCP_TIMEOUT:
+ g_value_set_uint64 (value, rtsp_client_sink->tcp_timeout);
+ break;
+ case PROP_LATENCY:
+ g_value_set_uint (value, rtsp_client_sink->latency);
+ break;
+ case PROP_RTX_TIME:
+ g_value_set_uint (value, rtsp_client_sink->rtx_time);
+ break;
+ case PROP_DO_RTSP_KEEP_ALIVE:
+ g_value_set_boolean (value, rtsp_client_sink->do_rtsp_keep_alive);
+ break;
+ case PROP_PROXY:
+ {
+ gchar *str;
+
+ if (rtsp_client_sink->proxy_host) {
+ str =
+ g_strdup_printf ("%s:%d", rtsp_client_sink->proxy_host,
+ rtsp_client_sink->proxy_port);
+ } else {
+ str = NULL;
+ }
+ g_value_take_string (value, str);
+ break;
+ }
+ case PROP_PROXY_ID:
+ g_value_set_string (value, rtsp_client_sink->prop_proxy_id);
+ break;
+ case PROP_PROXY_PW:
+ g_value_set_string (value, rtsp_client_sink->prop_proxy_pw);
+ break;
+ case PROP_RTP_BLOCKSIZE:
+ g_value_set_uint (value, rtsp_client_sink->rtp_blocksize);
+ break;
+ case PROP_USER_ID:
+ g_value_set_string (value, rtsp_client_sink->user_id);
+ break;
+ case PROP_USER_PW:
+ g_value_set_string (value, rtsp_client_sink->user_pw);
+ break;
+ case PROP_PORT_RANGE:
+ {
+ gchar *str;
+
+ if (rtsp_client_sink->client_port_range.min != 0) {
+ str = g_strdup_printf ("%u-%u", rtsp_client_sink->client_port_range.min,
+ rtsp_client_sink->client_port_range.max);
+ } else {
+ str = NULL;
+ }
+ g_value_take_string (value, str);
+ break;
+ }
+ case PROP_UDP_BUFFER_SIZE:
+ g_value_set_int (value, rtsp_client_sink->udp_buffer_size);
+ break;
+ case PROP_UDP_RECONNECT:
+ g_value_set_boolean (value, rtsp_client_sink->udp_reconnect);
+ break;
+ case PROP_MULTICAST_IFACE:
+ g_value_set_string (value, rtsp_client_sink->multi_iface);
+ break;
+ case PROP_SDES:
+ g_value_set_boxed (value, rtsp_client_sink->sdes);
+ break;
+ case PROP_TLS_VALIDATION_FLAGS:
+ g_value_set_flags (value, rtsp_client_sink->tls_validation_flags);
+ break;
+ case PROP_TLS_DATABASE:
+ g_value_set_object (value, rtsp_client_sink->tls_database);
+ break;
+ case PROP_TLS_INTERACTION:
+ g_value_set_object (value, rtsp_client_sink->tls_interaction);
+ break;
+ case PROP_NTP_TIME_SOURCE:
+ g_value_set_enum (value, rtsp_client_sink->ntp_time_source);
+ break;
+ case PROP_USER_AGENT:
+ g_value_set_string (value, rtsp_client_sink->user_agent);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ }
+
+ static const gchar *
+ get_aggregate_control (GstRTSPClientSink * sink)
+ {
+ const gchar *base;
+
+ if (sink->control)
+ base = sink->control;
+ else if (sink->content_base)
+ base = sink->content_base;
+ else if (sink->conninfo.url_str)
+ base = sink->conninfo.url_str;
+ else
+ base = "/";
+
+ return base;
+ }
+
+ static void
+ gst_rtsp_client_sink_cleanup (GstRTSPClientSink * sink)
+ {
+ GList *walk;
+
+ GST_DEBUG_OBJECT (sink, "cleanup");
+
+ gst_element_set_state (GST_ELEMENT (sink->internal_bin), GST_STATE_NULL);
+
+ /* Clean up any left over stream objects */
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) (walk->data);
+ if (context->stream_transport) {
+ gst_rtsp_stream_transport_set_active (context->stream_transport, FALSE);
+ gst_object_unref (context->stream_transport);
+ context->stream_transport = NULL;
+ }
+
+ if (context->stream) {
+ if (context->joined) {
+ gst_rtsp_stream_leave_bin (context->stream,
+ GST_BIN (sink->internal_bin), sink->rtpbin);
+ context->joined = FALSE;
+ }
+ gst_object_unref (context->stream);
+ context->stream = NULL;
+ }
+
+ if (context->srtcpparams) {
+ gst_caps_unref (context->srtcpparams);
+ context->srtcpparams = NULL;
+ }
+ g_free (context->conninfo.location);
+ context->conninfo.location = NULL;
+ }
+
+ if (sink->rtpbin) {
+ gst_element_set_state (sink->rtpbin, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN_CAST (sink->internal_bin), sink->rtpbin);
+ sink->rtpbin = NULL;
+ }
+
+ g_free (sink->content_base);
+ sink->content_base = NULL;
+
+ g_free (sink->control);
+ sink->control = NULL;
+
+ if (sink->range)
+ gst_rtsp_range_free (sink->range);
+ sink->range = NULL;
+
+ /* don't clear the SDP when it was used in the url */
+ if (sink->uri_sdp && !sink->from_sdp) {
+ gst_sdp_message_free (sink->uri_sdp);
+ sink->uri_sdp = NULL;
+ }
+
+ if (sink->provided_clock) {
+ gst_object_unref (sink->provided_clock);
+ sink->provided_clock = NULL;
+ }
+
+ g_free (sink->server_ip);
+ sink->server_ip = NULL;
+
+ sink->next_pad_id = 0;
+ sink->next_dyn_pt = 96;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_connection_send (GstRTSPClientSink * sink,
+ GstRTSPConnInfo * conninfo, GstRTSPMessage * message, gint64 timeout)
+ {
+ GstRTSPResult ret;
+
+ if (conninfo->connection) {
+ g_mutex_lock (&conninfo->send_lock);
+ ret =
+ gst_rtsp_connection_send_usec (conninfo->connection, message, timeout);
+ g_mutex_unlock (&conninfo->send_lock);
+ } else {
+ ret = GST_RTSP_ERROR;
+ }
+
+ return ret;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_connection_send_messages (GstRTSPClientSink * sink,
+ GstRTSPConnInfo * conninfo, GstRTSPMessage * messages, guint n_messages,
+ gint64 timeout)
+ {
+ GstRTSPResult ret;
+
+ if (conninfo->connection) {
+ g_mutex_lock (&conninfo->send_lock);
+ ret =
+ gst_rtsp_connection_send_messages_usec (conninfo->connection, messages,
+ n_messages, timeout);
+ g_mutex_unlock (&conninfo->send_lock);
+ } else {
+ ret = GST_RTSP_ERROR;
+ }
+
+ return ret;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_connection_receive (GstRTSPClientSink * sink,
+ GstRTSPConnInfo * conninfo, GstRTSPMessage * message, gint64 timeout)
+ {
+ GstRTSPResult ret;
+
+ if (conninfo->connection) {
+ g_mutex_lock (&conninfo->recv_lock);
+ ret = gst_rtsp_connection_receive_usec (conninfo->connection, message,
+ timeout);
+ g_mutex_unlock (&conninfo->recv_lock);
+ } else {
+ ret = GST_RTSP_ERROR;
+ }
+
+ return ret;
+ }
+
+ static gboolean
+ accept_certificate_cb (GTlsConnection * conn, GTlsCertificate * peer_cert,
+ GTlsCertificateFlags errors, gpointer user_data)
+ {
+ GstRTSPClientSink *sink = user_data;
+ gboolean accept = FALSE;
+
+ g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_ACCEPT_CERTIFICATE],
+ 0, conn, peer_cert, errors, &accept);
+
+ return accept;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_conninfo_connect (GstRTSPClientSink * sink, GstRTSPConnInfo * info,
+ gboolean async)
+ {
+ GstRTSPResult res;
+
+ if (info->connection == NULL) {
+ if (info->url == NULL) {
+ GST_DEBUG_OBJECT (sink, "parsing uri (%s)...", info->location);
+ if ((res = gst_rtsp_url_parse (info->location, &info->url)) < 0)
+ goto parse_error;
+ }
+
+ /* create connection */
+ GST_DEBUG_OBJECT (sink, "creating connection (%s)...", info->location);
+ if ((res = gst_rtsp_connection_create (info->url, &info->connection)) < 0)
+ goto could_not_create;
+
+ if (info->url_str)
+ g_free (info->url_str);
+ info->url_str = gst_rtsp_url_get_request_uri (info->url);
+
+ GST_DEBUG_OBJECT (sink, "sanitized uri %s", info->url_str);
+
+ if (info->url->transports & GST_RTSP_LOWER_TRANS_TLS) {
+ if (!gst_rtsp_connection_set_tls_validation_flags (info->connection,
+ sink->tls_validation_flags))
+ GST_WARNING_OBJECT (sink, "Unable to set TLS validation flags");
+
+ if (sink->tls_database)
+ gst_rtsp_connection_set_tls_database (info->connection,
+ sink->tls_database);
+
+ if (sink->tls_interaction)
+ gst_rtsp_connection_set_tls_interaction (info->connection,
+ sink->tls_interaction);
+
+ gst_rtsp_connection_set_accept_certificate_func (info->connection,
+ accept_certificate_cb, sink, NULL);
+ }
+
+ if (info->url->transports & GST_RTSP_LOWER_TRANS_HTTP)
+ gst_rtsp_connection_set_tunneled (info->connection, TRUE);
+
+ if (sink->proxy_host) {
+ GST_DEBUG_OBJECT (sink, "setting proxy %s:%d", sink->proxy_host,
+ sink->proxy_port);
+ gst_rtsp_connection_set_proxy (info->connection, sink->proxy_host,
+ sink->proxy_port);
+ }
+ }
+
+ if (!info->connected) {
+ /* connect */
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "connect",
+ ("Connecting to %s", info->location));
+ GST_DEBUG_OBJECT (sink, "connecting (%s)...", info->location);
+ if ((res =
+ gst_rtsp_connection_connect_usec (info->connection,
+ sink->tcp_timeout)) < 0)
+ goto could_not_connect;
+
+ info->connected = TRUE;
+ }
+ return GST_RTSP_OK;
+
+ /* ERRORS */
+ parse_error:
+ {
+ GST_ERROR_OBJECT (sink, "No valid RTSP URL was provided");
+ return res;
+ }
+ could_not_create:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+ GST_ERROR_OBJECT (sink, "Could not create connection. (%s)", str);
+ g_free (str);
+ return res;
+ }
+ could_not_connect:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+ GST_ERROR_OBJECT (sink, "Could not connect to server. (%s)", str);
+ g_free (str);
+ return res;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_conninfo_close (GstRTSPClientSink * sink, GstRTSPConnInfo * info,
+ gboolean free)
+ {
+ GST_RTSP_STATE_LOCK (sink);
+ if (info->connected) {
+ GST_DEBUG_OBJECT (sink, "closing connection...");
+ gst_rtsp_connection_close (info->connection);
+ info->connected = FALSE;
+ }
+ if (free && info->connection) {
+ /* free connection */
+ GST_DEBUG_OBJECT (sink, "freeing connection...");
+ gst_rtsp_connection_free (info->connection);
+ g_mutex_lock (&sink->preroll_lock);
+ info->connection = NULL;
+ g_cond_broadcast (&sink->preroll_cond);
+ g_mutex_unlock (&sink->preroll_lock);
+ }
+ GST_RTSP_STATE_UNLOCK (sink);
+ return GST_RTSP_OK;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_conninfo_reconnect (GstRTSPClientSink * sink, GstRTSPConnInfo * info,
+ gboolean async)
+ {
+ GstRTSPResult res;
+
+ GST_DEBUG_OBJECT (sink, "reconnecting connection...");
+ gst_rtsp_conninfo_close (sink, info, FALSE);
+ res = gst_rtsp_conninfo_connect (sink, info, async);
+
+ return res;
+ }
+
+ static void
+ gst_rtsp_client_sink_connection_flush (GstRTSPClientSink * sink, gboolean flush)
+ {
+ GList *walk;
+
+ GST_DEBUG_OBJECT (sink, "set flushing %d", flush);
+ g_mutex_lock (&sink->preroll_lock);
+ if (sink->conninfo.connection && sink->conninfo.flushing != flush) {
+ GST_DEBUG_OBJECT (sink, "connection flush");
+ gst_rtsp_connection_flush (sink->conninfo.connection, flush);
+ sink->conninfo.flushing = flush;
+ }
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *stream = (GstRTSPStreamContext *) walk->data;
+ if (stream->conninfo.connection && stream->conninfo.flushing != flush) {
+ GST_DEBUG_OBJECT (sink, "stream %p flush", stream);
+ gst_rtsp_connection_flush (stream->conninfo.connection, flush);
+ stream->conninfo.flushing = flush;
+ }
+ }
+ g_cond_broadcast (&sink->preroll_cond);
+ g_mutex_unlock (&sink->preroll_lock);
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_init_request (GstRTSPClientSink * sink,
+ GstRTSPMessage * msg, GstRTSPMethod method, const gchar * uri)
+ {
+ GstRTSPResult res;
+
+ res = gst_rtsp_message_init_request (msg, method, uri);
+ if (res < 0)
+ return res;
+
+ /* set user-agent */
+ if (sink->user_agent)
+ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_USER_AGENT,
+ sink->user_agent);
+
+ return res;
+ }
+
+ /* FIXME, handle server request, reply with OK, for now */
+ static GstRTSPResult
+ gst_rtsp_client_sink_handle_request (GstRTSPClientSink * sink,
+ GstRTSPConnInfo * conninfo, GstRTSPMessage * request)
+ {
+ GstRTSPMessage response = { 0 };
+ GstRTSPResult res;
+
+ GST_DEBUG_OBJECT (sink, "got server request message");
+
+ if (sink->debug)
+ gst_rtsp_message_dump (request);
+
+ /* default implementation, send OK */
+ GST_DEBUG_OBJECT (sink, "prepare OK reply");
+ res =
+ gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, "OK",
+ request);
+ if (res < 0)
+ goto send_error;
+
+ /* let app parse and reply */
+ g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_HANDLE_REQUEST],
+ 0, request, &response);
+
+ if (sink->debug)
+ gst_rtsp_message_dump (&response);
+
+ res = gst_rtsp_client_sink_connection_send (sink, conninfo, &response, 0);
+ if (res < 0)
+ goto send_error;
+
+ gst_rtsp_message_unset (&response);
+
+ return GST_RTSP_OK;
+
+ /* ERRORS */
+ send_error:
+ {
+ gst_rtsp_message_unset (&response);
+ return res;
+ }
+ }
+
+ /* send server keep-alive */
+ static GstRTSPResult
+ gst_rtsp_client_sink_send_keep_alive (GstRTSPClientSink * sink)
+ {
+ GstRTSPMessage request = { 0 };
+ GstRTSPResult res;
+ GstRTSPMethod method;
+ const gchar *control;
+
+ if (sink->do_rtsp_keep_alive == FALSE) {
+ GST_DEBUG_OBJECT (sink, "do-rtsp-keep-alive is FALSE, not sending.");
+ gst_rtsp_connection_reset_timeout (sink->conninfo.connection);
+ return GST_RTSP_OK;
+ }
+
+ GST_DEBUG_OBJECT (sink, "creating server keep-alive");
+
+ /* find a method to use for keep-alive */
+ if (sink->methods & GST_RTSP_GET_PARAMETER)
+ method = GST_RTSP_GET_PARAMETER;
+ else
+ method = GST_RTSP_OPTIONS;
+
+ control = get_aggregate_control (sink);
+ if (control == NULL)
+ goto no_control;
+
+ res = gst_rtsp_client_sink_init_request (sink, &request, method, control);
+ if (res < 0)
+ goto send_error;
+
+ if (sink->debug)
+ gst_rtsp_message_dump (&request);
+
+ res =
+ gst_rtsp_client_sink_connection_send (sink, &sink->conninfo, &request, 0);
+ if (res < 0)
+ goto send_error;
+
+ gst_rtsp_connection_reset_timeout (sink->conninfo.connection);
+ gst_rtsp_message_unset (&request);
+
+ return GST_RTSP_OK;
+
+ /* ERRORS */
+ no_control:
+ {
+ GST_WARNING_OBJECT (sink, "no control url to send keepalive");
+ return GST_RTSP_OK;
+ }
+ send_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ gst_rtsp_message_unset (&request);
+ GST_ELEMENT_WARNING (sink, RESOURCE, WRITE, (NULL),
+ ("Could not send keep-alive. (%s)", str));
+ g_free (str);
+ return res;
+ }
+ }
+
+ static GstFlowReturn
+ gst_rtsp_client_sink_loop_rx (GstRTSPClientSink * sink)
+ {
+ GstRTSPResult res;
+ GstRTSPMessage message = { 0 };
+ gint retry = 0;
+
+ while (TRUE) {
+ gint64 timeout;
+
+ /* get the next timeout interval */
+ timeout = gst_rtsp_connection_next_timeout_usec (sink->conninfo.connection);
+
+ GST_DEBUG_OBJECT (sink, "doing receive with timeout %d seconds",
+ (gint) timeout / G_USEC_PER_SEC);
+
+ gst_rtsp_message_unset (&message);
+
+ /* we should continue reading the TCP socket because the server might
+ * send us requests. When the session timeout expires, we need to send a
+ * keep-alive request to keep the session open. */
+ res =
+ gst_rtsp_client_sink_connection_receive (sink,
+ &sink->conninfo, &message, timeout);
+
+ switch (res) {
+ case GST_RTSP_OK:
+ GST_DEBUG_OBJECT (sink, "we received a server message");
+ break;
+ case GST_RTSP_EINTR:
+ /* we got interrupted, see what we have to do */
+ goto interrupt;
+ case GST_RTSP_ETIMEOUT:
+ /* send keep-alive, ignore the result, a warning will be posted. */
+ GST_DEBUG_OBJECT (sink, "timeout, sending keep-alive");
+ if ((res =
+ gst_rtsp_client_sink_send_keep_alive (sink)) == GST_RTSP_EINTR)
+ goto interrupt;
+ continue;
+ case GST_RTSP_EEOF:
+ /* server closed the connection. not very fatal for UDP, reconnect and
+ * see what happens. */
+ GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
+ ("The server closed the connection."));
+ if (sink->udp_reconnect) {
+ if ((res =
+ gst_rtsp_conninfo_reconnect (sink, &sink->conninfo,
+ FALSE)) < 0)
+ goto connect_error;
+ } else {
+ goto server_eof;
+ }
+ continue;
+ break;
+ case GST_RTSP_ENET:
+ GST_DEBUG_OBJECT (sink, "An ethernet problem occured.");
+ default:
+ GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
+ ("Unhandled return value %d.", res));
+ goto receive_error;
+ }
+
+ switch (message.type) {
+ case GST_RTSP_MESSAGE_REQUEST:
+ /* server sends us a request message, handle it */
+ res =
+ gst_rtsp_client_sink_handle_request (sink,
+ &sink->conninfo, &message);
+ if (res == GST_RTSP_EEOF)
+ goto server_eof;
+ else if (res < 0)
+ goto handle_request_failed;
+ break;
+ case GST_RTSP_MESSAGE_RESPONSE:
+ /* we ignore response and data messages */
+ GST_DEBUG_OBJECT (sink, "ignoring response message");
+ if (sink->debug)
+ gst_rtsp_message_dump (&message);
+ if (message.type_data.response.code == GST_RTSP_STS_UNAUTHORIZED) {
+ GST_DEBUG_OBJECT (sink, "but is Unauthorized response ...");
+ if (gst_rtsp_client_sink_setup_auth (sink, &message) && !(retry++)) {
+ GST_DEBUG_OBJECT (sink, "so retrying keep-alive");
+ if ((res =
+ gst_rtsp_client_sink_send_keep_alive (sink)) ==
+ GST_RTSP_EINTR)
+ goto interrupt;
+ }
+ } else {
+ retry = 0;
+ }
+ break;
+ case GST_RTSP_MESSAGE_DATA:
+ /* we ignore response and data messages */
+ GST_DEBUG_OBJECT (sink, "ignoring data message");
+ break;
+ default:
+ GST_WARNING_OBJECT (sink, "ignoring unknown message type %d",
+ message.type);
+ break;
+ }
+ }
+ g_assert_not_reached ();
+
+ /* we get here when the connection got interrupted */
+ interrupt:
+ {
+ gst_rtsp_message_unset (&message);
+ GST_DEBUG_OBJECT (sink, "got interrupted");
+ return GST_FLOW_FLUSHING;
+ }
+ connect_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+ GstFlowReturn ret;
+
+ sink->conninfo.connected = FALSE;
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ_WRITE, (NULL),
+ ("Could not connect to server. (%s)", str));
+ g_free (str);
+ ret = GST_FLOW_ERROR;
+ } else {
+ ret = GST_FLOW_FLUSHING;
+ }
+ return ret;
+ }
+ receive_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Could not receive message. (%s)", str));
+ g_free (str);
+ return GST_FLOW_ERROR;
+ }
+ handle_request_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+ GstFlowReturn ret;
+
+ gst_rtsp_message_unset (&message);
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
+ ("Could not handle server message. (%s)", str));
+ g_free (str);
+ ret = GST_FLOW_ERROR;
+ } else {
+ ret = GST_FLOW_FLUSHING;
+ }
+ return ret;
+ }
+ server_eof:
+ {
+ GST_DEBUG_OBJECT (sink, "we got an eof from the server");
+ GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
+ ("The server closed the connection."));
+ sink->conninfo.connected = FALSE;
+ gst_rtsp_message_unset (&message);
+ return GST_FLOW_EOS;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_reconnect (GstRTSPClientSink * sink, gboolean async)
+ {
+ GstRTSPResult res = GST_RTSP_OK;
+ gboolean restart = FALSE;
+
+ GST_DEBUG_OBJECT (sink, "doing reconnect");
+
+ GST_FIXME_OBJECT (sink, "Reconnection is not yet implemented");
+
+ /* no need to restart, we're done */
+ if (!restart)
+ goto done;
+
+ /* we can try only TCP now */
+ sink->cur_protocols = GST_RTSP_LOWER_TRANS_TCP;
+
+ /* close and cleanup our state */
+ if ((res = gst_rtsp_client_sink_close (sink, async, FALSE)) < 0)
+ goto done;
+
+ /* see if we have TCP left to try. Also don't try TCP when we were configured
+ * with an SDP. */
+ if (!(sink->protocols & GST_RTSP_LOWER_TRANS_TCP) || sink->from_sdp)
+ goto no_protocols;
+
+ /* We post a warning message now to inform the user
+ * that nothing happened. It's most likely a firewall thing. */
+ GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
+ ("Could not receive any UDP packets for %.4f seconds, maybe your "
+ "firewall is blocking it. Retrying using a TCP connection.",
+ gst_guint64_to_gdouble (sink->udp_timeout / 1000000.0)));
+
+ /* open new connection using tcp */
+ if (gst_rtsp_client_sink_open (sink, async) < 0)
+ goto open_failed;
+
+ /* start recording */
+ if (gst_rtsp_client_sink_record (sink, async) < 0)
+ goto play_failed;
+
+ done:
+ return res;
+
+ /* ERRORS */
+ no_protocols:
+ {
+ sink->cur_protocols = 0;
+ /* no transport possible, post an error and stop */
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Could not receive any UDP packets for %.4f seconds, maybe your "
+ "firewall is blocking it. No other protocols to try.",
+ gst_guint64_to_gdouble (sink->udp_timeout / 1000000.0)));
+ return GST_RTSP_ERROR;
+ }
+ open_failed:
+ {
+ GST_DEBUG_OBJECT (sink, "open failed");
+ return GST_RTSP_OK;
+ }
+ play_failed:
+ {
+ GST_DEBUG_OBJECT (sink, "play failed");
+ return GST_RTSP_OK;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_loop_start_cmd (GstRTSPClientSink * sink, gint cmd)
+ {
+ switch (cmd) {
+ case CMD_OPEN:
+ GST_ELEMENT_PROGRESS (sink, START, "open", ("Opening Stream"));
+ break;
+ case CMD_RECORD:
+ GST_ELEMENT_PROGRESS (sink, START, "request", ("Sending RECORD request"));
+ break;
+ case CMD_PAUSE:
+ GST_ELEMENT_PROGRESS (sink, START, "request", ("Sending PAUSE request"));
+ break;
+ case CMD_CLOSE:
+ GST_ELEMENT_PROGRESS (sink, START, "close", ("Closing Stream"));
+ break;
+ default:
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_loop_complete_cmd (GstRTSPClientSink * sink, gint cmd)
+ {
+ switch (cmd) {
+ case CMD_OPEN:
+ GST_ELEMENT_PROGRESS (sink, COMPLETE, "open", ("Opened Stream"));
+ break;
+ case CMD_RECORD:
+ GST_ELEMENT_PROGRESS (sink, COMPLETE, "request", ("Sent RECORD request"));
+ break;
+ case CMD_PAUSE:
+ GST_ELEMENT_PROGRESS (sink, COMPLETE, "request", ("Sent PAUSE request"));
+ break;
+ case CMD_CLOSE:
+ GST_ELEMENT_PROGRESS (sink, COMPLETE, "close", ("Closed Stream"));
+ break;
+ default:
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_loop_cancel_cmd (GstRTSPClientSink * sink, gint cmd)
+ {
+ switch (cmd) {
+ case CMD_OPEN:
+ GST_ELEMENT_PROGRESS (sink, CANCELED, "open", ("Open canceled"));
+ break;
+ case CMD_RECORD:
+ GST_ELEMENT_PROGRESS (sink, CANCELED, "request", ("RECORD canceled"));
+ break;
+ case CMD_PAUSE:
+ GST_ELEMENT_PROGRESS (sink, CANCELED, "request", ("PAUSE canceled"));
+ break;
+ case CMD_CLOSE:
+ GST_ELEMENT_PROGRESS (sink, CANCELED, "close", ("Close canceled"));
+ break;
+ default:
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_loop_error_cmd (GstRTSPClientSink * sink, gint cmd)
+ {
+ switch (cmd) {
+ case CMD_OPEN:
+ GST_ELEMENT_PROGRESS (sink, ERROR, "open", ("Open failed"));
+ break;
+ case CMD_RECORD:
+ GST_ELEMENT_PROGRESS (sink, ERROR, "request", ("RECORD failed"));
+ break;
+ case CMD_PAUSE:
+ GST_ELEMENT_PROGRESS (sink, ERROR, "request", ("PAUSE failed"));
+ break;
+ case CMD_CLOSE:
+ GST_ELEMENT_PROGRESS (sink, ERROR, "close", ("Close failed"));
+ break;
+ default:
+ break;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_loop_end_cmd (GstRTSPClientSink * sink, gint cmd,
+ GstRTSPResult ret)
+ {
+ if (ret == GST_RTSP_OK)
+ gst_rtsp_client_sink_loop_complete_cmd (sink, cmd);
+ else if (ret == GST_RTSP_EINTR)
+ gst_rtsp_client_sink_loop_cancel_cmd (sink, cmd);
+ else
+ gst_rtsp_client_sink_loop_error_cmd (sink, cmd);
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_loop_send_cmd (GstRTSPClientSink * sink, gint cmd,
+ gint mask)
+ {
+ gint old;
+ gboolean flushed = FALSE;
+
+ /* start new request */
+ gst_rtsp_client_sink_loop_start_cmd (sink, cmd);
+
+ GST_DEBUG_OBJECT (sink, "sending cmd %s", cmd_to_string (cmd));
+
+ GST_OBJECT_LOCK (sink);
+ old = sink->pending_cmd;
+ if (old == CMD_RECONNECT) {
+ GST_DEBUG_OBJECT (sink, "ignore, we were reconnecting");
+ cmd = CMD_RECONNECT;
+ }
+ if (old != CMD_WAIT) {
+ sink->pending_cmd = CMD_WAIT;
+ GST_OBJECT_UNLOCK (sink);
+ /* cancel previous request */
+ GST_DEBUG_OBJECT (sink, "cancel previous request %s", cmd_to_string (old));
+ gst_rtsp_client_sink_loop_cancel_cmd (sink, old);
+ GST_OBJECT_LOCK (sink);
+ }
+ sink->pending_cmd = cmd;
+ /* interrupt if allowed */
+ if (sink->busy_cmd & mask) {
+ GST_DEBUG_OBJECT (sink, "connection flush busy %s",
+ cmd_to_string (sink->busy_cmd));
+ gst_rtsp_client_sink_connection_flush (sink, TRUE);
+ flushed = TRUE;
+ } else {
+ GST_DEBUG_OBJECT (sink, "not interrupting busy cmd %s",
+ cmd_to_string (sink->busy_cmd));
+ }
+ if (sink->task)
+ gst_task_start (sink->task);
+ GST_OBJECT_UNLOCK (sink);
+
+ return flushed;
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_loop (GstRTSPClientSink * sink)
+ {
+ GstFlowReturn ret;
+
+ if (!sink->conninfo.connection || !sink->conninfo.connected)
+ goto no_connection;
+
+ ret = gst_rtsp_client_sink_loop_rx (sink);
+ if (ret != GST_FLOW_OK)
+ goto pause;
+
+ return TRUE;
+
+ /* ERRORS */
+ no_connection:
+ {
+ GST_WARNING_OBJECT (sink, "we are not connected");
+ ret = GST_FLOW_FLUSHING;
+ goto pause;
+ }
+ pause:
+ {
+ const gchar *reason = gst_flow_get_name (ret);
+
+ GST_DEBUG_OBJECT (sink, "pausing task, reason %s", reason);
+ gst_rtsp_client_sink_loop_send_cmd (sink, CMD_WAIT, CMD_LOOP);
+ return FALSE;
+ }
+ }
+
+ #ifndef GST_DISABLE_GST_DEBUG
+ static const gchar *
+ gst_rtsp_auth_method_to_string (GstRTSPAuthMethod method)
+ {
+ gint index = 0;
+
+ while (method != 0) {
+ index++;
+ method >>= 1;
+ }
+ switch (index) {
+ case 0:
+ return "None";
+ case 1:
+ return "Basic";
+ case 2:
+ return "Digest";
+ }
+
+ return "Unknown";
+ }
+ #endif
+
+ /* Parse a WWW-Authenticate Response header and determine the
+ * available authentication methods
+ *
+ * This code should also cope with the fact that each WWW-Authenticate
+ * header can contain multiple challenge methods + tokens
+ *
+ * At the moment, for Basic auth, we just do a minimal check and don't
+ * even parse out the realm */
+ static void
+ gst_rtsp_client_sink_parse_auth_hdr (GstRTSPMessage * response,
+ GstRTSPAuthMethod * methods, GstRTSPConnection * conn, gboolean * stale)
+ {
+ GstRTSPAuthCredential **credentials, **credential;
+
+ g_return_if_fail (response != NULL);
+ g_return_if_fail (methods != NULL);
+ g_return_if_fail (stale != NULL);
+
+ credentials =
+ gst_rtsp_message_parse_auth_credentials (response,
+ GST_RTSP_HDR_WWW_AUTHENTICATE);
+ if (!credentials)
+ return;
+
+ credential = credentials;
+ while (*credential) {
+ if ((*credential)->scheme == GST_RTSP_AUTH_BASIC) {
+ *methods |= GST_RTSP_AUTH_BASIC;
+ } else if ((*credential)->scheme == GST_RTSP_AUTH_DIGEST) {
+ GstRTSPAuthParam **param = (*credential)->params;
+
+ *methods |= GST_RTSP_AUTH_DIGEST;
+
+ gst_rtsp_connection_clear_auth_params (conn);
+ *stale = FALSE;
+
+ while (*param) {
+ if (strcmp ((*param)->name, "stale") == 0
+ && g_ascii_strcasecmp ((*param)->value, "TRUE") == 0)
+ *stale = TRUE;
+ gst_rtsp_connection_set_auth_param (conn, (*param)->name,
+ (*param)->value);
+ param++;
+ }
+ }
+
+ credential++;
+ }
+
+ gst_rtsp_auth_credentials_free (credentials);
+ }
+
+ /**
+ * gst_rtsp_client_sink_setup_auth:
+ * @src: the rtsp source
+ *
+ * Configure a username and password and auth method on the
+ * connection object based on a response we received from the
+ * peer.
+ *
+ * Currently, this requires that a username and password were supplied
+ * in the uri. In the future, they may be requested on demand by sending
+ * a message up the bus.
+ *
+ * Returns: TRUE if authentication information could be set up correctly.
+ */
+ static gboolean
+ gst_rtsp_client_sink_setup_auth (GstRTSPClientSink * sink,
+ GstRTSPMessage * response)
+ {
+ gchar *user = NULL;
+ gchar *pass = NULL;
+ GstRTSPAuthMethod avail_methods = GST_RTSP_AUTH_NONE;
+ GstRTSPAuthMethod method;
+ GstRTSPResult auth_result;
+ GstRTSPUrl *url;
+ GstRTSPConnection *conn;
+ gboolean stale = FALSE;
+
+ conn = sink->conninfo.connection;
+
+ /* Identify the available auth methods and see if any are supported */
+ gst_rtsp_client_sink_parse_auth_hdr (response, &avail_methods, conn, &stale);
+
+ if (avail_methods == GST_RTSP_AUTH_NONE)
+ goto no_auth_available;
+
+ /* For digest auth, if the response indicates that the session
+ * data are stale, we just update them in the connection object and
+ * return TRUE to retry the request */
+ if (stale)
+ sink->tried_url_auth = FALSE;
+
+ url = gst_rtsp_connection_get_url (conn);
+
+ /* Do we have username and password available? */
+ if (url != NULL && !sink->tried_url_auth && url->user != NULL
+ && url->passwd != NULL) {
+ user = url->user;
+ pass = url->passwd;
+ sink->tried_url_auth = TRUE;
+ GST_DEBUG_OBJECT (sink,
+ "Attempting authentication using credentials from the URL");
+ } else {
+ user = sink->user_id;
+ pass = sink->user_pw;
+ GST_DEBUG_OBJECT (sink,
+ "Attempting authentication using credentials from the properties");
+ }
+
+ /* FIXME: If the url didn't contain username and password or we tried them
+ * already, request a username and passwd from the application via some kind
+ * of credentials request message */
+
+ /* If we don't have a username and passwd at this point, bail out. */
+ if (user == NULL || pass == NULL)
+ goto no_user_pass;
+
+ /* Try to configure for each available authentication method, strongest to
+ * weakest */
+ for (method = GST_RTSP_AUTH_MAX; method != GST_RTSP_AUTH_NONE; method >>= 1) {
+ /* Check if this method is available on the server */
+ if ((method & avail_methods) == 0)
+ continue;
+
+ /* Pass the credentials to the connection to try on the next request */
+ auth_result = gst_rtsp_connection_set_auth (conn, method, user, pass);
+ /* INVAL indicates an invalid username/passwd were supplied, so we'll just
+ * ignore it and end up retrying later */
+ if (auth_result == GST_RTSP_OK || auth_result == GST_RTSP_EINVAL) {
+ GST_DEBUG_OBJECT (sink, "Attempting %s authentication",
+ gst_rtsp_auth_method_to_string (method));
+ break;
+ }
+ }
+
+ if (method == GST_RTSP_AUTH_NONE)
+ goto no_auth_available;
+
+ return TRUE;
+
+ no_auth_available:
+ {
+ /* Output an error indicating that we couldn't connect because there were
+ * no supported authentication protocols */
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ, (NULL),
+ ("No supported authentication protocol was found"));
+ return FALSE;
+ }
+ no_user_pass:
+ {
+ /* We don't fire an error message, we just return FALSE and let the
+ * normal NOT_AUTHORIZED error be propagated */
+ return FALSE;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_try_send (GstRTSPClientSink * sink,
+ GstRTSPConnInfo * conninfo, GstRTSPMessage * requests,
+ guint n_requests, GstRTSPMessage * response, GstRTSPStatusCode * code)
+ {
+ GstRTSPResult res;
+ GstRTSPStatusCode thecode;
+ gchar *content_base = NULL;
+ gint try = 0;
+
+ g_assert (n_requests == 1 || response == NULL);
+
+ again:
+ GST_DEBUG_OBJECT (sink, "sending message");
+
+ if (sink->debug && n_requests == 1)
+ gst_rtsp_message_dump (&requests[0]);
+
+ g_mutex_lock (&sink->send_lock);
+
+ res =
+ gst_rtsp_client_sink_connection_send_messages (sink, conninfo, requests,
+ n_requests, sink->tcp_timeout);
+ if (res < 0) {
+ g_mutex_unlock (&sink->send_lock);
+ goto send_error;
+ }
+
+ gst_rtsp_connection_reset_timeout (conninfo->connection);
+
+ /* See if we should handle the response */
+ if (response == NULL) {
+ g_mutex_unlock (&sink->send_lock);
+ return GST_RTSP_OK;
+ }
+ next:
+ res =
+ gst_rtsp_client_sink_connection_receive (sink, conninfo, response,
+ sink->tcp_timeout);
+
+ g_mutex_unlock (&sink->send_lock);
+
+ if (res < 0)
+ goto receive_error;
+
+ if (sink->debug)
+ gst_rtsp_message_dump (response);
+
+
+ switch (response->type) {
+ case GST_RTSP_MESSAGE_REQUEST:
+ res = gst_rtsp_client_sink_handle_request (sink, conninfo, response);
+ if (res == GST_RTSP_EEOF)
+ goto server_eof;
+ else if (res < 0)
+ goto handle_request_failed;
+ g_mutex_lock (&sink->send_lock);
+ goto next;
+ case GST_RTSP_MESSAGE_RESPONSE:
+ /* ok, a response is good */
+ GST_DEBUG_OBJECT (sink, "received response message");
+ break;
+ case GST_RTSP_MESSAGE_DATA:
+ /* we ignore data messages */
+ GST_DEBUG_OBJECT (sink, "ignoring data message");
+ g_mutex_lock (&sink->send_lock);
+ goto next;
+ default:
+ GST_WARNING_OBJECT (sink, "ignoring unknown message type %d",
+ response->type);
+ g_mutex_lock (&sink->send_lock);
+ goto next;
+ }
+
+ thecode = response->type_data.response.code;
+
+ GST_DEBUG_OBJECT (sink, "got response message %d", thecode);
+
+ /* if the caller wanted the result code, we store it. */
+ if (code)
+ *code = thecode;
+
+ /* If the request didn't succeed, bail out before doing any more */
+ if (thecode != GST_RTSP_STS_OK)
+ return GST_RTSP_OK;
+
+ /* store new content base if any */
+ gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_BASE,
+ &content_base, 0);
+ if (content_base) {
+ g_free (sink->content_base);
+ sink->content_base = g_strdup (content_base);
+ }
+
+ return GST_RTSP_OK;
+
+ /* ERRORS */
+ send_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
+ ("Could not send message. (%s)", str));
+ } else {
+ GST_WARNING_OBJECT (sink, "send interrupted");
+ }
+ g_free (str);
+ return res;
+ }
+ receive_error:
+ {
+ switch (res) {
+ case GST_RTSP_EEOF:
+ GST_WARNING_OBJECT (sink, "server closed connection");
+ if ((try == 0) && !sink->interleaved && sink->udp_reconnect) {
+ try++;
+ /* if reconnect succeeds, try again */
+ if ((res =
+ gst_rtsp_conninfo_reconnect (sink, &sink->conninfo,
+ FALSE)) == 0)
+ goto again;
+ }
+ /* only try once after reconnect, then fallthrough and error out */
+ default:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Could not receive message. (%s)", str));
+ } else {
+ GST_WARNING_OBJECT (sink, "receive interrupted");
+ }
+ g_free (str);
+ break;
+ }
+ }
+ return res;
+ }
+ handle_request_failed:
+ {
+ /* ERROR was posted */
+ gst_rtsp_message_unset (response);
+ return res;
+ }
+ server_eof:
+ {
+ GST_DEBUG_OBJECT (sink, "we got an eof from the server");
+ GST_ELEMENT_WARNING (sink, RESOURCE, READ, (NULL),
+ ("The server closed the connection."));
+ gst_rtsp_message_unset (response);
+ return res;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_set_state (GstRTSPClientSink * sink, GstState state)
+ {
+ GST_DEBUG_OBJECT (sink, "Setting internal state to %s",
+ gst_element_state_get_name (state));
+ gst_element_set_state (GST_ELEMENT (sink->internal_bin), state);
+ }
+
+ /**
+ * gst_rtsp_client_sink_send:
+ * @src: the rtsp source
+ * @conn: the connection to send on
+ * @request: must point to a valid request
+ * @response: must point to an empty #GstRTSPMessage
+ * @code: an optional code result
+ *
+ * send @request and retrieve the response in @response. optionally @code can be
+ * non-NULL in which case it will contain the status code of the response.
+ *
+ * If This function returns #GST_RTSP_OK, @response will contain a valid response
+ * message that should be cleaned with gst_rtsp_message_unset() after usage.
+ *
+ * If @code is NULL, this function will return #GST_RTSP_ERROR (with an invalid
+ * @response message) if the response code was not 200 (OK).
+ *
+ * If the attempt results in an authentication failure, then this will attempt
+ * to retrieve authentication credentials via gst_rtsp_client_sink_setup_auth and retry
+ * the request.
+ *
+ * Returns: #GST_RTSP_OK if the processing was successful.
+ */
+ static GstRTSPResult
+ gst_rtsp_client_sink_send (GstRTSPClientSink * sink, GstRTSPConnInfo * conninfo,
+ GstRTSPMessage * request, GstRTSPMessage * response,
+ GstRTSPStatusCode * code)
+ {
+ GstRTSPStatusCode int_code = GST_RTSP_STS_OK;
+ GstRTSPResult res = GST_RTSP_ERROR;
+ gint count;
+ gboolean retry;
+ GstRTSPMethod method = GST_RTSP_INVALID;
+
+ count = 0;
+ do {
+ retry = FALSE;
+
+ /* make sure we don't loop forever */
+ if (count++ > 8)
+ break;
+
+ /* save method so we can disable it when the server complains */
+ method = request->type_data.request.method;
+
+ if ((res =
+ gst_rtsp_client_sink_try_send (sink, conninfo, request, 1, response,
+ &int_code)) < 0)
+ goto error;
+
+ switch (int_code) {
+ case GST_RTSP_STS_UNAUTHORIZED:
+ if (gst_rtsp_client_sink_setup_auth (sink, response)) {
+ /* Try the request/response again after configuring the auth info
+ * and loop again */
+ retry = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ } while (retry == TRUE);
+
+ /* If the user requested the code, let them handle errors, otherwise
+ * post an error below */
+ if (code != NULL)
+ *code = int_code;
+ else if (int_code != GST_RTSP_STS_OK)
+ goto error_response;
+
+ return res;
+
+ /* ERRORS */
+ error:
+ {
+ GST_DEBUG_OBJECT (sink, "got error %d", res);
+ return res;
+ }
+ error_response:
+ {
+ res = GST_RTSP_ERROR;
+
+ switch (response->type_data.response.code) {
+ case GST_RTSP_STS_NOT_FOUND:
+ GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL), ("%s",
+ response->type_data.response.reason));
+ break;
+ case GST_RTSP_STS_UNAUTHORIZED:
+ GST_ELEMENT_ERROR (sink, RESOURCE, NOT_AUTHORIZED, (NULL), ("%s",
+ response->type_data.response.reason));
+ break;
+ case GST_RTSP_STS_MOVED_PERMANENTLY:
+ case GST_RTSP_STS_MOVE_TEMPORARILY:
+ {
+ gchar *new_location;
+ GstRTSPLowerTrans transports;
+
+ GST_DEBUG_OBJECT (sink, "got redirection");
+ /* if we don't have a Location Header, we must error */
+ if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_LOCATION,
+ &new_location, 0) < 0)
+ break;
+
+ /* When we receive a redirect result, we go back to the INIT state after
+ * parsing the new URI. The caller should do the needed steps to issue
+ * a new setup when it detects this state change. */
+ GST_DEBUG_OBJECT (sink, "redirection to %s", new_location);
+
+ /* save current transports */
+ if (sink->conninfo.url)
+ transports = sink->conninfo.url->transports;
+ else
+ transports = GST_RTSP_LOWER_TRANS_UNKNOWN;
+
+ gst_rtsp_client_sink_uri_set_uri (GST_URI_HANDLER (sink), new_location,
+ NULL);
+
+ /* set old transports */
+ if (sink->conninfo.url && transports != GST_RTSP_LOWER_TRANS_UNKNOWN)
+ sink->conninfo.url->transports = transports;
+
+ sink->need_redirect = TRUE;
+ sink->state = GST_RTSP_STATE_INIT;
+ res = GST_RTSP_OK;
+ break;
+ }
+ case GST_RTSP_STS_NOT_ACCEPTABLE:
+ case GST_RTSP_STS_NOT_IMPLEMENTED:
+ case GST_RTSP_STS_METHOD_NOT_ALLOWED:
+ GST_WARNING_OBJECT (sink, "got NOT IMPLEMENTED, disable method %s",
+ gst_rtsp_method_as_text (method));
+ sink->methods &= ~method;
+ res = GST_RTSP_OK;
+ break;
+ default:
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Got error response: %d (%s).", response->type_data.response.code,
+ response->type_data.response.reason));
+ break;
+ }
+ /* if we return ERROR we should unset the response ourselves */
+ if (res == GST_RTSP_ERROR)
+ gst_rtsp_message_unset (response);
+
+ return res;
+ }
+ }
+
+ /* parse the response and collect all the supported methods. We need this
+ * information so that we don't try to send an unsupported request to the
+ * server.
+ */
+ static gboolean
+ gst_rtsp_client_sink_parse_methods (GstRTSPClientSink * sink,
+ GstRTSPMessage * response)
+ {
+ GstRTSPHeaderField field;
+ gchar *respoptions;
+ gint indx = 0;
+
+ /* reset supported methods */
+ sink->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;
+
+ sink->methods |= gst_rtsp_options_from_text (respoptions);
+
+ indx++;
+ }
+
+ if (sink->methods == 0) {
+ /* neither Allow nor Public are required, assume the server supports
+ * at least SETUP. */
+ GST_DEBUG_OBJECT (sink, "could not get OPTIONS");
+ sink->methods = GST_RTSP_SETUP;
+ }
+
+ /* Even if the server replied, and didn't say it supports
+ * RECORD|ANNOUNCE, try anyway by assuming it does */
+ sink->methods |= GST_RTSP_ANNOUNCE | GST_RTSP_RECORD;
+
+ if (!(sink->methods & GST_RTSP_SETUP))
+ goto no_setup;
+
+ return TRUE;
+
+ /* ERRORS */
+ no_setup:
+ {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ, (NULL),
+ ("Server does not support SETUP."));
+ return FALSE;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_connect_to_server (GstRTSPClientSink * sink,
+ gboolean async)
+ {
+ GstRTSPResult res;
+ GstRTSPMessage request = { 0 };
+ GstRTSPMessage response = { 0 };
+ GSocket *conn_socket;
+ GSocketAddress *sa;
+ GInetAddress *ia;
+
+ sink->need_redirect = FALSE;
+
+ /* can't continue without a valid url */
+ if (G_UNLIKELY (sink->conninfo.url == NULL)) {
+ res = GST_RTSP_EINVAL;
+ goto no_url;
+ }
+ sink->tried_url_auth = FALSE;
+
+ if ((res = gst_rtsp_conninfo_connect (sink, &sink->conninfo, async)) < 0)
+ goto connect_failed;
+
+ conn_socket = gst_rtsp_connection_get_read_socket (sink->conninfo.connection);
+ sa = g_socket_get_remote_address (conn_socket, NULL);
+ ia = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sa));
+
+ sink->server_ip = g_inet_address_to_string (ia);
+
+ g_object_unref (sa);
+
+ /* create OPTIONS */
+ GST_DEBUG_OBJECT (sink, "create options...");
+ res =
+ gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_OPTIONS,
+ sink->conninfo.url_str);
+ if (res < 0)
+ goto create_request_failed;
+
+ /* send OPTIONS */
+ GST_DEBUG_OBJECT (sink, "send options...");
+
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "open",
+ ("Retrieving server options"));
+
+ if ((res =
+ gst_rtsp_client_sink_send (sink, &sink->conninfo, &request,
+ &response, NULL)) < 0)
+ goto send_error;
+
+ /* parse OPTIONS */
+ if (!gst_rtsp_client_sink_parse_methods (sink, &response))
+ goto methods_error;
+
+ /* FIXME: Do we need to handle REDIRECT responses for OPTIONS? */
+
+ /* clean up any messages */
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+
+ return res;
+
+ /* ERRORS */
+ no_url:
+ {
+ GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL),
+ ("No valid RTSP URL was provided"));
+ goto cleanup_error;
+ }
+ connect_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ_WRITE, (NULL),
+ ("Failed to connect. (%s)", str));
+ } else {
+ GST_WARNING_OBJECT (sink, "connect interrupted");
+ }
+ g_free (str);
+ goto cleanup_error;
+ }
+ create_request_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
+ ("Could not create request. (%s)", str));
+ g_free (str);
+ goto cleanup_error;
+ }
+ send_error:
+ {
+ /* Don't post a message - the rtsp_send method will have
+ * taken care of it because we passed NULL for the response code */
+ goto cleanup_error;
+ }
+ methods_error:
+ {
+ /* error was posted */
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ cleanup_error:
+ {
+ if (sink->conninfo.connection) {
+ GST_DEBUG_OBJECT (sink, "free connection");
+ gst_rtsp_conninfo_close (sink, &sink->conninfo, TRUE);
+ }
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+ return res;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_open (GstRTSPClientSink * sink, gboolean async)
+ {
+ GstRTSPResult ret;
+
+ sink->methods =
+ GST_RTSP_SETUP | GST_RTSP_RECORD | GST_RTSP_PAUSE | GST_RTSP_TEARDOWN;
+
+ g_mutex_lock (&sink->open_conn_lock);
+ sink->open_conn_start = TRUE;
+ g_cond_broadcast (&sink->open_conn_cond);
+ GST_DEBUG_OBJECT (sink, "connection to server started");
+ g_mutex_unlock (&sink->open_conn_lock);
+
+ if ((ret = gst_rtsp_client_sink_connect_to_server (sink, async)) < 0)
+ goto open_failed;
+
+ if (async)
+ gst_rtsp_client_sink_loop_end_cmd (sink, CMD_OPEN, ret);
+
+ return ret;
+
+ /* ERRORS */
+ open_failed:
+ {
+ GST_WARNING_OBJECT (sink, "Failed to connect to server");
+ sink->open_error = TRUE;
+ if (async)
+ gst_rtsp_client_sink_loop_end_cmd (sink, CMD_OPEN, ret);
+ return ret;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_close (GstRTSPClientSink * sink, gboolean async,
+ gboolean only_close)
+ {
+ GstRTSPMessage request = { 0 };
+ GstRTSPMessage response = { 0 };
+ GstRTSPResult res = GST_RTSP_OK;
+ GList *walk;
+ const gchar *control;
+
+ GST_DEBUG_OBJECT (sink, "TEARDOWN...");
+
+ gst_rtsp_client_sink_set_state (sink, GST_STATE_NULL);
+
+ if (sink->state < GST_RTSP_STATE_READY) {
+ GST_DEBUG_OBJECT (sink, "not ready, doing cleanup");
+ goto close;
+ }
+
+ if (only_close)
+ goto close;
+
+ /* construct a control url */
+ control = get_aggregate_control (sink);
+
+ if (!(sink->methods & (GST_RTSP_RECORD | GST_RTSP_TEARDOWN)))
+ goto not_supported;
+
+ /* stop streaming */
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+
+ if (context->stream_transport) {
+ gst_rtsp_stream_transport_set_active (context->stream_transport, FALSE);
+ gst_object_unref (context->stream_transport);
+ context->stream_transport = NULL;
+ }
+
+ if (context->joined) {
+ gst_rtsp_stream_leave_bin (context->stream, GST_BIN (sink->internal_bin),
+ sink->rtpbin);
+ context->joined = FALSE;
+ }
+ }
+
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+ const gchar *setup_url;
+ GstRTSPConnInfo *info;
+
+ GST_DEBUG_OBJECT (sink, "Looking at stream %p for teardown",
+ context->stream);
+
+ /* try aggregate control first but do non-aggregate control otherwise */
+ if (control)
+ setup_url = control;
+ else if ((setup_url = context->conninfo.location) == NULL) {
+ GST_DEBUG_OBJECT (sink, "Skipping TEARDOWN stream %p - no setup URL",
+ context->stream);
+ continue;
+ }
+
+ if (sink->conninfo.connection) {
+ info = &sink->conninfo;
+ } else if (context->conninfo.connection) {
+ info = &context->conninfo;
+ } else {
+ continue;
+ }
+ if (!info->connected)
+ goto next;
+
+ /* do TEARDOWN */
+ GST_DEBUG_OBJECT (sink, "Sending teardown for stream %p at URL %s",
+ context->stream, setup_url);
+ res =
+ gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_TEARDOWN,
+ setup_url);
+ if (res < 0)
+ goto create_request_failed;
+
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "close", ("Closing stream"));
+
+ if ((res =
+ gst_rtsp_client_sink_send (sink, info, &request,
+ &response, NULL)) < 0)
+ goto send_error;
+
+ /* FIXME, parse result? */
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+
+ next:
+ /* early exit when we did aggregate control */
+ if (control)
+ break;
+ }
+
+ close:
+ /* close connections */
+ GST_DEBUG_OBJECT (sink, "closing connection...");
+ gst_rtsp_conninfo_close (sink, &sink->conninfo, TRUE);
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *stream = (GstRTSPStreamContext *) walk->data;
+ gst_rtsp_conninfo_close (sink, &stream->conninfo, TRUE);
+ }
+
+ /* cleanup */
+ gst_rtsp_client_sink_cleanup (sink);
+
+ sink->state = GST_RTSP_STATE_INVALID;
+
+ if (async)
+ gst_rtsp_client_sink_loop_end_cmd (sink, CMD_CLOSE, res);
+
+ return res;
+
+ /* ERRORS */
+ create_request_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
+ ("Could not create request. (%s)", str));
+ g_free (str);
+ goto close;
+ }
+ send_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ gst_rtsp_message_unset (&request);
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
+ ("Could not send message. (%s)", str));
+ } else {
+ GST_WARNING_OBJECT (sink, "TEARDOWN interrupted");
+ }
+ g_free (str);
+ goto close;
+ }
+ not_supported:
+ {
+ GST_DEBUG_OBJECT (sink,
+ "TEARDOWN and PLAY not supported, can't do TEARDOWN");
+ goto close;
+ }
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_configure_manager (GstRTSPClientSink * sink)
+ {
+ GstElement *rtpbin;
+ GstStateChangeReturn ret;
+
+ rtpbin = sink->rtpbin;
+
+ if (rtpbin == NULL) {
+ GObjectClass *klass;
+
+ rtpbin = gst_element_factory_make ("rtpbin", NULL);
+ if (rtpbin == NULL)
+ goto no_rtpbin;
+
+ gst_bin_add (GST_BIN_CAST (sink->internal_bin), rtpbin);
+
+ sink->rtpbin = rtpbin;
+
+ /* Any more settings we should configure on rtpbin here? */
+ g_object_set (sink->rtpbin, "latency", sink->latency, NULL);
+
+ klass = G_OBJECT_GET_CLASS (G_OBJECT (rtpbin));
+
+ if (g_object_class_find_property (klass, "ntp-time-source")) {
+ g_object_set (sink->rtpbin, "ntp-time-source", sink->ntp_time_source,
+ NULL);
+ }
+
+ if (sink->sdes && g_object_class_find_property (klass, "sdes")) {
+ g_object_set (sink->rtpbin, "sdes", sink->sdes, NULL);
+ }
+
+ g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_NEW_MANAGER], 0,
+ sink->rtpbin);
+ }
+
+ ret = gst_element_set_state (rtpbin, GST_STATE_PAUSED);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto start_manager_failure;
+
+ return TRUE;
+
+ no_rtpbin:
+ {
+ GST_WARNING ("no rtpbin element");
+ g_warning ("failed to create element 'rtpbin', check your installation");
+ return FALSE;
+ }
+ start_manager_failure:
+ {
+ GST_DEBUG_OBJECT (sink, "could not start session manager");
+ gst_bin_remove (GST_BIN_CAST (sink->internal_bin), rtpbin);
+ return FALSE;
+ }
+ }
+
+ static GstElement *
+ request_aux_sender (GstElement * rtpbin, guint sessid, GstRTSPClientSink * sink)
+ {
+ GstRTSPStream *stream = NULL;
+ GstElement *ret = NULL;
+ GList *walk;
+
+ GST_RTSP_STATE_LOCK (sink);
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+
+ if (sessid == gst_rtsp_stream_get_index (context->stream)) {
+ stream = context->stream;
+ break;
+ }
+ }
+
+ if (stream != NULL) {
+ GST_DEBUG_OBJECT (sink, "Creating aux sender for stream %u", sessid);
+ ret = gst_rtsp_stream_request_aux_sender (stream, sessid);
+ }
+
+ GST_RTSP_STATE_UNLOCK (sink);
+
+ return ret;
+ }
+
+ static GstElement *
+ request_fec_encoder (GstElement * rtpbin, guint sessid,
+ GstRTSPClientSink * sink)
+ {
+ GstRTSPStream *stream = NULL;
+ GstElement *ret = NULL;
+ GList *walk;
+
+ GST_RTSP_STATE_LOCK (sink);
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+
+ if (sessid == gst_rtsp_stream_get_index (context->stream)) {
+ stream = context->stream;
+ break;
+ }
+ }
+
+ if (stream != NULL) {
+ ret = gst_rtsp_stream_request_ulpfec_encoder (stream, sessid);
+ }
+
+ GST_RTSP_STATE_UNLOCK (sink);
+
+ return ret;
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_collect_streams (GstRTSPClientSink * sink)
+ {
+ GstRTSPStreamContext *context;
+ GList *walk;
+ const gchar *base;
+ gchar *stream_path;
+ GstUri *base_uri, *uri;
+
+ GST_DEBUG_OBJECT (sink, "Collecting stream information");
+
+ if (!gst_rtsp_client_sink_configure_manager (sink))
+ return FALSE;
+
+ base = get_aggregate_control (sink);
+
+ base_uri = gst_uri_from_string (base);
+ if (!base_uri) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL),
+ ("Could not parse uri %s", base));
+ return FALSE;
+ }
+
+ g_mutex_lock (&sink->preroll_lock);
+ while (sink->contexts == NULL && !sink->conninfo.flushing) {
+ g_cond_wait (&sink->preroll_cond, &sink->preroll_lock);
+ }
+ g_mutex_unlock (&sink->preroll_lock);
+
+ /* FIXME: Need different locking - need to protect against pad releases
+ * and potential state changes ruining things here */
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstPad *srcpad;
+
+ context = (GstRTSPStreamContext *) walk->data;
+ if (context->stream)
+ continue;
+
+ g_mutex_lock (&sink->preroll_lock);
+ while (!context->prerolled && !sink->conninfo.flushing) {
+ GST_DEBUG_OBJECT (sink, "Waiting for caps on stream %d", context->index);
+ g_cond_wait (&sink->preroll_cond, &sink->preroll_lock);
+ }
+ if (sink->conninfo.flushing) {
+ g_mutex_unlock (&sink->preroll_lock);
+ break;
+ }
+ g_mutex_unlock (&sink->preroll_lock);
+
+ if (context->payloader == NULL)
+ continue;
+
+ srcpad = gst_element_get_static_pad (context->payloader, "src");
+
+ GST_DEBUG_OBJECT (sink, "Creating stream object for stream %d",
+ context->index);
+ context->stream =
+ gst_rtsp_client_sink_create_stream (sink, context, context->payloader,
+ srcpad);
+
+ /* append stream index to uri path */
+ g_free (context->conninfo.location);
+
+ stream_path = g_strdup_printf ("stream=%d", context->index);
+ uri = gst_uri_copy (base_uri);
+ gst_uri_append_path (uri, stream_path);
+
+ context->conninfo.location = gst_uri_to_string (uri);
+ gst_uri_unref (uri);
+ g_free (stream_path);
+
+ if (sink->rtx_time > 0) {
+ /* enable retransmission by setting rtprtxsend as the "aux" element of rtpbin */
+ g_signal_connect (sink->rtpbin, "request-aux-sender",
+ (GCallback) request_aux_sender, sink);
+ }
+
+ g_signal_connect (sink->rtpbin, "request-fec-encoder",
+ (GCallback) request_fec_encoder, sink);
+
+ if (!gst_rtsp_stream_join_bin (context->stream,
+ GST_BIN (sink->internal_bin), sink->rtpbin, GST_STATE_PAUSED)) {
+ goto join_bin_failed;
+ }
+ context->joined = TRUE;
+
+ /* Block the stream, as it does not have any transport parts yet */
+ gst_rtsp_stream_set_blocked (context->stream, TRUE);
+
+ /* Let the stream object receive data */
+ gst_pad_remove_probe (srcpad, context->payloader_block_id);
+
+ gst_object_unref (srcpad);
+ }
+
+ /* Now wait for the preroll of the rtp bin */
+ g_mutex_lock (&sink->preroll_lock);
+ while (!sink->prerolled && sink->conninfo.connection
+ && !sink->conninfo.flushing) {
+ GST_LOG_OBJECT (sink, "Waiting for preroll before continuing");
+ g_cond_wait (&sink->preroll_cond, &sink->preroll_lock);
+ }
+ GST_LOG_OBJECT (sink, "Marking streams as collected");
+ sink->streams_collected = TRUE;
+ g_mutex_unlock (&sink->preroll_lock);
+
+ gst_uri_unref (base_uri);
+ return TRUE;
+
+ join_bin_failed:
+
+ gst_uri_unref (base_uri);
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Could not start stream %d", context->index));
+ return FALSE;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_create_transports_string (GstRTSPClientSink * sink,
+ GstRTSPStreamContext * context, GSocketFamily family,
+ GstRTSPLowerTrans protocols, GstRTSPProfile profiles, gchar ** transports)
+ {
+ GString *result;
+ GstRTSPStream *stream = context->stream;
+ gboolean first = TRUE;
+
+ /* the default RTSP transports */
+ result = g_string_new ("RTP");
+
+ while (profiles != 0) {
+ if (!first)
+ g_string_append (result, ",RTP");
+
+ if (profiles & GST_RTSP_PROFILE_SAVPF) {
+ g_string_append (result, "/SAVPF");
+ profiles &= ~GST_RTSP_PROFILE_SAVPF;
+ } else if (profiles & GST_RTSP_PROFILE_SAVP) {
+ g_string_append (result, "/SAVP");
+ profiles &= ~GST_RTSP_PROFILE_SAVP;
+ } else if (profiles & GST_RTSP_PROFILE_AVPF) {
+ g_string_append (result, "/AVPF");
+ profiles &= ~GST_RTSP_PROFILE_AVPF;
+ } else if (profiles & GST_RTSP_PROFILE_AVP) {
+ g_string_append (result, "/AVP");
+ profiles &= ~GST_RTSP_PROFILE_AVP;
+ } else {
+ GST_WARNING_OBJECT (sink, "Unimplemented profile(s) 0x%x", profiles);
+ break;
+ }
+
+ if (protocols & GST_RTSP_LOWER_TRANS_UDP) {
+ GstRTSPRange ports;
+
+ GST_DEBUG_OBJECT (sink, "adding UDP unicast");
+ gst_rtsp_stream_get_server_port (stream, &ports, family);
+
+ g_string_append_printf (result, "/UDP;unicast;client_port=%d-%d",
+ ports.min, ports.max);
+ } else if (protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) {
+ GstRTSPAddress *addr =
+ gst_rtsp_stream_get_multicast_address (stream, family);
+ if (addr) {
+ GST_DEBUG_OBJECT (sink, "adding UDP multicast");
+ g_string_append_printf (result, "/UDP;multicast;client_port=%d-%d",
+ addr->port, addr->port + addr->n_ports - 1);
+ gst_rtsp_address_free (addr);
+ }
+ } else if (protocols & GST_RTSP_LOWER_TRANS_TCP) {
+ GST_DEBUG_OBJECT (sink, "adding TCP");
+ g_string_append_printf (result, "/TCP;unicast;interleaved=%d-%d",
+ sink->free_channel, sink->free_channel + 1);
+ }
+
+ g_string_append (result, ";mode=RECORD");
+ /* FIXME: Support appending too:
+ if (sink->append)
+ g_string_append (result, ";append");
+ */
+
+ first = FALSE;
+ }
+
+ if (first) {
+ /* No valid transport could be constructed */
+ GST_ERROR_OBJECT (sink, "No supported profiles configured");
+ goto fail;
+ }
+
+ *transports = g_string_free (result, FALSE);
+
+ GST_DEBUG_OBJECT (sink, "prepared transports %s", GST_STR_NULL (*transports));
+
+ return GST_RTSP_OK;
+ fail:
+ g_string_free (result, TRUE);
+ return GST_RTSP_ERROR;
+ }
+
+ static GstCaps *
+ signal_get_srtcp_params (GstRTSPClientSink * sink,
+ GstRTSPStreamContext * context)
+ {
+ GstCaps *caps = NULL;
+
+ g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_REQUEST_RTCP_KEY], 0,
+ context->index, &caps);
+
+ if (caps != NULL)
+ GST_DEBUG_OBJECT (sink, "SRTP parameters received");
+
+ return caps;
+ }
+
+ static gchar *
+ gst_rtsp_client_sink_stream_make_keymgmt (GstRTSPClientSink * sink,
+ GstRTSPStreamContext * context)
+ {
+ gchar *base64, *result = NULL;
+ GstMIKEYMessage *mikey_msg;
+
+ context->srtcpparams = signal_get_srtcp_params (sink, context);
+ if (context->srtcpparams == NULL)
+ context->srtcpparams = gst_rtsp_stream_get_caps (context->stream);
+
+ mikey_msg = gst_mikey_message_new_from_caps (context->srtcpparams);
+ if (mikey_msg) {
+ guint send_ssrc, send_rtx_ssrc;
+ const GstStructure *s = gst_caps_get_structure (context->srtcpparams, 0);
+
+ /* add policy '0' for our SSRC */
+ gst_rtsp_stream_get_ssrc (context->stream, &send_ssrc);
+ GST_LOG_OBJECT (sink, "Stream %p ssrc %x", context->stream, send_ssrc);
+ gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_ssrc, 0);
+
+ if (gst_structure_get_uint (s, "rtx-ssrc", &send_rtx_ssrc))
+ gst_mikey_message_add_cs_srtp (mikey_msg, 0, send_rtx_ssrc, 0);
+
+ base64 = gst_mikey_message_base64_encode (mikey_msg);
+ gst_mikey_message_unref (mikey_msg);
+
+ if (base64) {
+ result = gst_sdp_make_keymgmt (context->conninfo.location, base64);
+ g_free (base64);
+ }
+ }
+
+ return result;
+ }
+
+ /* masks to be kept in sync with the hardcoded protocol order of preference
+ * in code below */
+ static const guint protocol_masks[] = {
+ GST_RTSP_LOWER_TRANS_UDP,
+ GST_RTSP_LOWER_TRANS_UDP_MCAST,
+ GST_RTSP_LOWER_TRANS_TCP,
+ 0
+ };
+
+ /* Same for profile_masks */
+ static const guint profile_masks[] = {
+ GST_RTSP_PROFILE_SAVPF,
+ GST_RTSP_PROFILE_SAVP,
+ GST_RTSP_PROFILE_AVPF,
+ GST_RTSP_PROFILE_AVP,
+ 0
+ };
+
+ static gboolean
+ do_send_data (GstBuffer * buffer, guint8 channel,
+ GstRTSPStreamContext * context)
+ {
+ GstRTSPClientSink *sink = context->parent;
+ GstRTSPMessage message = { 0 };
+ GstRTSPResult res = GST_RTSP_OK;
+
+ gst_rtsp_message_init_data (&message, channel);
+
+ gst_rtsp_message_set_body_buffer (&message, buffer);
+
+ res =
+ gst_rtsp_client_sink_try_send (sink, &sink->conninfo, &message, 1,
+ NULL, NULL);
+
+ gst_rtsp_message_unset (&message);
+
+ gst_rtsp_stream_transport_message_sent (context->stream_transport);
+
+ return res == GST_RTSP_OK;
+ }
+
+ static gboolean
+ do_send_data_list (GstBufferList * buffer_list, guint8 channel,
+ GstRTSPStreamContext * context)
+ {
+ GstRTSPClientSink *sink = context->parent;
+ GstRTSPResult res = GST_RTSP_OK;
+ guint i, n = gst_buffer_list_length (buffer_list);
+ GstRTSPMessage *messages = g_newa (GstRTSPMessage, n);
+
+ memset (messages, 0, n * sizeof (GstRTSPMessage));
+
+ for (i = 0; i < n; i++) {
+ GstBuffer *buffer = gst_buffer_list_get (buffer_list, i);
+
+ gst_rtsp_message_init_data (&messages[i], channel);
+
+ gst_rtsp_message_set_body_buffer (&messages[i], buffer);
+ }
+
+ res =
+ gst_rtsp_client_sink_try_send (sink, &sink->conninfo, messages, n,
+ NULL, NULL);
+
+ for (i = 0; i < n; i++) {
+ gst_rtsp_message_unset (&messages[i]);
+ gst_rtsp_stream_transport_message_sent (context->stream_transport);
+ }
+
+ return res == GST_RTSP_OK;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_setup_streams (GstRTSPClientSink * sink, gboolean async)
+ {
+ GstRTSPResult res = GST_RTSP_ERROR;
+ GstRTSPMessage request = { 0 };
+ GstRTSPMessage response = { 0 };
+ GstRTSPLowerTrans protocols;
+ GstRTSPStatusCode code;
+ GSocketFamily family;
+ GSocketAddress *sa;
+ GSocket *conn_socket;
+ GstRTSPUrl *url;
+ GList *walk;
+ gchar *hval;
+
+ if (sink->conninfo.connection) {
+ url = gst_rtsp_connection_get_url (sink->conninfo.connection);
+ /* we initially allow all configured lower transports. based on the URL
+ * transports and the replies from the server we narrow them down. */
+ protocols = url->transports & sink->cur_protocols;
+ } else {
+ url = NULL;
+ protocols = sink->cur_protocols;
+ }
+
+ if (protocols == 0)
+ goto no_protocols;
+
+ GST_RTSP_STATE_LOCK (sink);
+
+ if (G_UNLIKELY (sink->contexts == NULL))
+ goto no_streams;
+
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+ GstRTSPStream *stream;
+
+ GstRTSPConnInfo *info;
+ GstRTSPProfile profiles;
+ GstRTSPProfile cur_profile;
+ gchar *transports;
+ gint retry = 0;
+ guint profile_mask = 0;
+ guint mask = 0;
+ GstCaps *caps;
+ const GstSDPMedia *media;
+
+ stream = context->stream;
+ profiles = gst_rtsp_stream_get_profiles (stream);
+
+ caps = gst_rtsp_stream_get_caps (stream);
+ if (caps == NULL) {
+ GST_DEBUG_OBJECT (sink, "skipping stream %p, no caps", stream);
+ continue;
+ }
+ gst_caps_unref (caps);
+ media = gst_sdp_message_get_media (&sink->cursdp, context->sdp_index);
+ if (media == NULL) {
+ GST_DEBUG_OBJECT (sink, "skipping stream %p, no SDP info", stream);
+ continue;
+ }
+
+ /* skip setup if we have no URL for it */
+ if (context->conninfo.location == NULL) {
+ GST_DEBUG_OBJECT (sink, "skipping stream %p, no setup", stream);
+ continue;
+ }
+
+ if (sink->conninfo.connection == NULL) {
+ if (!gst_rtsp_conninfo_connect (sink, &context->conninfo, async)) {
+ GST_DEBUG_OBJECT (sink, "skipping stream %p, failed to connect",
+ stream);
+ continue;
+ }
+ info = &context->conninfo;
+ } else {
+ info = &sink->conninfo;
+ }
+ GST_DEBUG_OBJECT (sink, "doing setup of stream %p with %s", stream,
+ context->conninfo.location);
+
+ conn_socket = gst_rtsp_connection_get_read_socket (info->connection);
+ sa = g_socket_get_local_address (conn_socket, NULL);
+ family = g_socket_address_get_family (sa);
+ g_object_unref (sa);
+
+ next_protocol:
+ /* first selectable profile */
+ while (profile_masks[profile_mask]
+ && !(profiles & profile_masks[profile_mask]))
+ profile_mask++;
+ if (!profile_masks[profile_mask])
+ goto no_profiles;
+
+ /* first selectable protocol */
+ while (protocol_masks[mask] && !(protocols & protocol_masks[mask]))
+ mask++;
+ if (!protocol_masks[mask])
+ goto no_protocols;
+
+ retry:
+ GST_DEBUG_OBJECT (sink, "protocols = 0x%x, protocol mask = 0x%x", protocols,
+ protocol_masks[mask]);
+ /* create a string with first transport in line */
+ transports = NULL;
+ cur_profile = profiles & profile_masks[profile_mask];
+ res = gst_rtsp_client_sink_create_transports_string (sink, context, family,
+ protocols & protocol_masks[mask], cur_profile, &transports);
+ if (res < 0 || transports == NULL)
+ goto setup_transport_failed;
+
+ if (strlen (transports) == 0) {
+ g_free (transports);
+ GST_DEBUG_OBJECT (sink, "no transports found");
+ mask++;
+ profile_mask = 0;
+ goto next_protocol;
+ }
+
+ GST_DEBUG_OBJECT (sink, "transport is %s", GST_STR_NULL (transports));
+
+ /* create SETUP request */
+ res =
+ gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_SETUP,
+ context->conninfo.location);
+ if (res < 0) {
+ g_free (transports);
+ goto create_request_failed;
+ }
+
+ /* set up keys */
+ if (cur_profile == GST_RTSP_PROFILE_SAVP ||
+ cur_profile == GST_RTSP_PROFILE_SAVPF) {
+ hval = gst_rtsp_client_sink_stream_make_keymgmt (sink, context);
+ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_KEYMGMT, hval);
+ }
+
+ /* if the user wants a non default RTP packet size we add the blocksize
+ * parameter */
+ if (sink->rtp_blocksize > 0) {
+ hval = g_strdup_printf ("%d", sink->rtp_blocksize);
+ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_BLOCKSIZE, hval);
+ }
+
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "request", ("SETUP stream %d",
+ context->index));
+
+ {
+ GstRTSPTransport *transport;
+
+ gst_rtsp_transport_new (&transport);
+ if (gst_rtsp_transport_parse (transports, transport) != GST_RTSP_OK)
+ goto parse_transport_failed;
+ if (transport->lower_transport != GST_RTSP_LOWER_TRANS_TCP) {
+ if (!gst_rtsp_stream_allocate_udp_sockets (stream, family, transport,
+ FALSE)) {
+ gst_rtsp_transport_free (transport);
+ goto allocate_udp_ports_failed;
+ }
+ }
+ if (!gst_rtsp_stream_complete_stream (stream, transport)) {
+ gst_rtsp_transport_free (transport);
+ goto complete_stream_failed;
+ }
+
+ gst_rtsp_transport_free (transport);
+ gst_rtsp_stream_set_blocked (stream, FALSE);
+ }
+
+ /* FIXME:
+ * the creation of the transports string depends on
+ * calling stream_get_server_port, which only starts returning
+ * something meaningful after a call to stream_allocate_udp_sockets
+ * has been made, this function expects a transport that we parse
+ * from the transport string ...
+ *
+ * Significant refactoring is in order, but does not look entirely
+ * trivial, for now we put a band aid on and create a second transport
+ * string after the stream has been completed, to pass it in
+ * the request headers instead of the previous, incomplete one.
+ */
+ g_free (transports);
+ transports = NULL;
+ res = gst_rtsp_client_sink_create_transports_string (sink, context, family,
+ protocols & protocol_masks[mask], cur_profile, &transports);
+
+ if (res < 0 || transports == NULL)
+ goto setup_transport_failed;
+
+ /* select transport */
+ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports);
+
+ /* handle the code ourselves */
+ res = gst_rtsp_client_sink_send (sink, info, &request, &response, &code);
+ if (res < 0)
+ goto send_error;
+
+ switch (code) {
+ case GST_RTSP_STS_OK:
+ break;
+ case GST_RTSP_STS_UNSUPPORTED_TRANSPORT:
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+
+ /* Try another profile. If no more, move to the next protocol */
+ profile_mask++;
+ while (profile_masks[profile_mask]
+ && !(profiles & profile_masks[profile_mask]))
+ profile_mask++;
+ if (profile_masks[profile_mask])
+ goto retry;
+
+ /* select next available protocol, give up on this stream if none */
+ /* Reset profiles to try: */
+ profile_mask = 0;
+
+ mask++;
+ while (protocol_masks[mask] && !(protocols & protocol_masks[mask]))
+ mask++;
+ if (!protocol_masks[mask])
+ continue;
+ else
+ goto retry;
+ default:
+ goto response_error;
+ }
+
+ /* parse response transport */
+ {
+ gchar *resptrans = NULL;
+ GstRTSPTransport *transport;
+
+ gst_rtsp_message_get_header (&response, GST_RTSP_HDR_TRANSPORT,
+ &resptrans, 0);
+ if (!resptrans) {
+ goto no_transport;
+ }
+
+ gst_rtsp_transport_new (&transport);
+
+ /* parse transport, go to next stream on parse error */
+ if (gst_rtsp_transport_parse (resptrans, transport) != GST_RTSP_OK) {
+ GST_WARNING_OBJECT (sink, "failed to parse transport %s", resptrans);
+ goto next;
+ }
+
+ /* update allowed transports for other streams. once the transport of
+ * one stream has been determined, we make sure that all other streams
+ * are configured in the same way */
+ switch (transport->lower_transport) {
+ case GST_RTSP_LOWER_TRANS_TCP:
+ GST_DEBUG_OBJECT (sink, "stream %p as TCP interleaved", stream);
+ protocols = GST_RTSP_LOWER_TRANS_TCP;
+ sink->interleaved = TRUE;
+ /* update free channels */
+ sink->free_channel =
+ MAX (transport->interleaved.min, sink->free_channel);
+ sink->free_channel =
+ MAX (transport->interleaved.max, sink->free_channel);
+ sink->free_channel++;
+ break;
+ case GST_RTSP_LOWER_TRANS_UDP_MCAST:
+ /* only allow multicast for other streams */
+ GST_DEBUG_OBJECT (sink, "stream %p as UDP multicast", stream);
+ protocols = GST_RTSP_LOWER_TRANS_UDP_MCAST;
+ break;
+ case GST_RTSP_LOWER_TRANS_UDP:
+ /* only allow unicast for other streams */
+ GST_DEBUG_OBJECT (sink, "stream %p as UDP unicast", stream);
+ protocols = GST_RTSP_LOWER_TRANS_UDP;
+ /* Update transport with server destination if not provided by the server */
+ if (transport->destination == NULL) {
+ transport->destination = g_strdup (sink->server_ip);
+ }
+ break;
+ default:
+ GST_DEBUG_OBJECT (sink, "stream %p unknown transport %d", stream,
+ transport->lower_transport);
+ break;
+ }
+
+ if (!retry) {
+ GST_DEBUG ("Configuring the stream transport for stream %d",
+ context->index);
+ if (context->stream_transport == NULL)
+ context->stream_transport =
+ gst_rtsp_stream_transport_new (stream, transport);
+ else
+ gst_rtsp_stream_transport_set_transport (context->stream_transport,
+ transport);
+
+ if (transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP) {
+ /* our callbacks to send data on this TCP connection */
+ gst_rtsp_stream_transport_set_callbacks (context->stream_transport,
+ (GstRTSPSendFunc) do_send_data,
+ (GstRTSPSendFunc) do_send_data, context, NULL);
+ gst_rtsp_stream_transport_set_list_callbacks
+ (context->stream_transport,
+ (GstRTSPSendListFunc) do_send_data_list,
+ (GstRTSPSendListFunc) do_send_data_list, context, NULL);
+ }
+
+ /* The stream_transport now owns the transport */
+ transport = NULL;
+
+ gst_rtsp_stream_transport_set_active (context->stream_transport, TRUE);
+ }
+ next:
+ if (transport)
+ gst_rtsp_transport_free (transport);
+ /* clean up used RTSP messages */
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+ }
+ }
+ GST_RTSP_STATE_UNLOCK (sink);
+
+ /* store the transport protocol that was configured */
+ sink->cur_protocols = protocols;
+
+ return res;
+
+ no_streams:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
+ ("SDP contains no streams"));
+ return GST_RTSP_ERROR;
+ }
+ setup_transport_failed:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
+ ("Could not setup transport."));
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ no_profiles:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ /* no transport possible, post an error and stop */
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Could not connect to server, no profiles left"));
+ return GST_RTSP_ERROR;
+ }
+ no_protocols:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ /* no transport possible, post an error and stop */
+ GST_ELEMENT_ERROR (sink, RESOURCE, READ, (NULL),
+ ("Could not connect to server, no protocols left"));
+ return GST_RTSP_ERROR;
+ }
+ no_transport:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
+ ("Server did not select transport."));
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ create_request_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
+ ("Could not create request. (%s)", str));
+ g_free (str);
+ goto cleanup_error;
+ }
+ parse_transport_failed:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
+ ("Could not parse transport."));
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ allocate_udp_ports_failed:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
+ ("Could not parse transport."));
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ complete_stream_failed:
+ {
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL),
+ ("Could not parse transport."));
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ send_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_RTSP_STATE_UNLOCK (sink);
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
+ ("Could not send message. (%s)", str));
+ } else {
+ GST_WARNING_OBJECT (sink, "send interrupted");
+ }
+ g_free (str);
+ goto cleanup_error;
+ }
+ response_error:
+ {
+ const gchar *str = gst_rtsp_status_as_text (code);
+
+ GST_RTSP_STATE_UNLOCK (sink);
+ GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
+ ("Error (%d): %s", code, GST_STR_NULL (str)));
+ res = GST_RTSP_ERROR;
+ goto cleanup_error;
+ }
+ cleanup_error:
+ {
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+ return res;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_ensure_open (GstRTSPClientSink * sink, gboolean async)
+ {
+ GstRTSPResult res = GST_RTSP_OK;
+
+ if (sink->state < GST_RTSP_STATE_READY) {
+ res = GST_RTSP_ERROR;
+ if (sink->open_error) {
+ GST_DEBUG_OBJECT (sink, "the stream was in error");
+ goto done;
+ }
+ if (async)
+ gst_rtsp_client_sink_loop_start_cmd (sink, CMD_OPEN);
+
+ if ((res = gst_rtsp_client_sink_open (sink, async)) < 0) {
+ GST_DEBUG_OBJECT (sink, "failed to open stream");
+ goto done;
+ }
+ }
+
+ done:
+ return res;
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_is_stopping (GstRTSPClientSink * sink)
+ {
+ gboolean is_stopping;
+
+ GST_OBJECT_LOCK (sink);
+ is_stopping = sink->task == NULL;
+ GST_OBJECT_UNLOCK (sink);
+
+ return is_stopping;
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_record (GstRTSPClientSink * sink, gboolean async)
+ {
+ GstRTSPMessage request = { 0 };
+ GstRTSPMessage response = { 0 };
+ GstRTSPResult res = GST_RTSP_OK;
+ GstSDPMessage *sdp;
+ guint sdp_index = 0;
+ GstSDPInfo info = { 0, };
+ gchar *keymgmt;
+ guint i;
+
+ const gchar *proto;
+ gchar *sess_id, *client_ip, *str;
+ GSocketAddress *sa;
+ GInetAddress *ia;
+ GSocket *conn_socket;
+ GList *walk;
+
+ g_mutex_lock (&sink->preroll_lock);
+ if (sink->state == GST_RTSP_STATE_PLAYING) {
+ /* Already recording, don't send another request */
+ GST_LOG_OBJECT (sink, "Already in RECORD. Skipping duplicate request.");
+ g_mutex_unlock (&sink->preroll_lock);
+ goto done;
+ }
+ g_mutex_unlock (&sink->preroll_lock);
+
+ /* Collect all our input streams and create
+ * stream objects before actually returning.
+ * The streams are blocked at this point as we do not have any transport
+ * parts yet. */
+ gst_rtsp_client_sink_collect_streams (sink);
+
+ if (gst_rtsp_client_sink_is_stopping (sink)) {
+ GST_INFO_OBJECT (sink, "task stopped, shutting down");
+ return GST_RTSP_EINTR;
+ }
+
+ g_mutex_lock (&sink->block_streams_lock);
+ /* Wait for streams to be blocked */
+ while (sink->n_streams_blocked < g_list_length (sink->contexts)
+ && !gst_rtsp_client_sink_is_stopping (sink)) {
+ GST_DEBUG_OBJECT (sink, "waiting for streams to be blocked");
+ g_cond_wait (&sink->block_streams_cond, &sink->block_streams_lock);
+ }
+ g_mutex_unlock (&sink->block_streams_lock);
+
+ if (gst_rtsp_client_sink_is_stopping (sink)) {
+ GST_INFO_OBJECT (sink, "task stopped, shutting down");
+ return GST_RTSP_EINTR;
+ }
+
+ /* Send announce, then setup for all streams */
+ gst_sdp_message_init (&sink->cursdp);
+ sdp = &sink->cursdp;
+
+ /* some standard things first */
+ gst_sdp_message_set_version (sdp, "0");
+
+ /* session ID doesn't have to be super-unique in this case */
+ sess_id = g_strdup_printf ("%u", g_random_int ());
+
++ if (sink->conninfo.connection == NULL) {
++ g_free (sess_id);
+ return GST_RTSP_ERROR;
++ }
+
+ conn_socket = gst_rtsp_connection_get_read_socket (sink->conninfo.connection);
+
+ sa = g_socket_get_local_address (conn_socket, NULL);
+ ia = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sa));
+ client_ip = g_inet_address_to_string (ia);
+ if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV6) {
+ info.is_ipv6 = TRUE;
+ proto = "IP6";
+ } else if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV4)
+ proto = "IP4";
+ else
+ g_assert_not_reached ();
+ g_object_unref (sa);
+
+ /* FIXME: Should this actually be the server's IP or ours? */
+ info.server_ip = sink->server_ip;
+
+ gst_sdp_message_set_origin (sdp, "-", sess_id, "1", "IN", proto, client_ip);
+
+ gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer");
+ gst_sdp_message_set_information (sdp, "rtspclientsink");
+ gst_sdp_message_add_time (sdp, "0", "0", NULL);
+ gst_sdp_message_add_attribute (sdp, "tool", "GStreamer");
+
+ /* add stream */
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+
+ gst_rtsp_sdp_from_stream (sdp, &info, context->stream);
+ context->sdp_index = sdp_index++;
+ }
+
+ g_free (sess_id);
+ g_free (client_ip);
+
+ /* send ANNOUNCE request */
+ GST_DEBUG_OBJECT (sink, "create ANNOUNCE request...");
+ res =
+ gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_ANNOUNCE,
+ sink->conninfo.url_str);
+ if (res < 0)
+ goto create_request_failed;
+
+ g_signal_emit (sink, gst_rtsp_client_sink_signals[SIGNAL_UPDATE_SDP], 0, sdp);
+
+ gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CONTENT_TYPE,
+ "application/sdp");
+
+ /* add SDP to the request body */
+ str = gst_sdp_message_as_text (sdp);
+ gst_rtsp_message_take_body (&request, (guint8 *) str, strlen (str));
+
+ /* send ANNOUNCE */
+ GST_DEBUG_OBJECT (sink, "sending announce...");
+
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "record",
+ ("Sending server stream info"));
+
+ if ((res =
+ gst_rtsp_client_sink_send (sink, &sink->conninfo, &request,
+ &response, NULL)) < 0)
+ goto send_error;
+
+ /* parse the keymgmt */
+ i = 0;
+ walk = sink->contexts;
+ while (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_KEYMGMT,
+ &keymgmt, i++) == GST_RTSP_OK) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+ walk = g_list_next (walk);
+ if (!gst_rtsp_stream_handle_keymgmt (context->stream, keymgmt))
+ goto keymgmt_error;
+ }
+
+ /* send setup for all streams */
+ if ((res = gst_rtsp_client_sink_setup_streams (sink, async)) < 0)
+ goto setup_failed;
+
+ res = gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_RECORD,
+ sink->conninfo.url_str);
+
+ if (res < 0)
+ goto create_request_failed;
+
+ #if 0 /* FIXME: Configure a range based on input segments? */
+ if (src->need_range) {
+ hval = gen_range_header (src, segment);
+
+ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval);
+ }
+
+ if (segment->rate != 1.0) {
+ gchar hval[G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_ascii_dtostr (hval, sizeof (hval), segment->rate);
+ if (src->skip)
+ gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, hval);
+ else
+ gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, hval);
+ }
+ #endif
+
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "record", ("Starting recording"));
+ if ((res =
+ gst_rtsp_client_sink_send (sink, &sink->conninfo, &request,
+ &response, NULL)) < 0)
+ goto send_error;
+
+ #if 0 /* FIXME: Check if servers return these for record: */
+ /* parse the RTP-Info header field (if ANY) to get the base seqnum and timestamp
+ * for the RTP packets. If this is not present, we assume all starts from 0...
+ * This is info for the RTP session manager that we pass to it in caps. */
+ hval_idx = 0;
+ while (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTP_INFO,
+ &hval, hval_idx++) == GST_RTSP_OK)
+ gst_rtspsrc_parse_rtpinfo (src, hval);
+
+ /* some servers indicate RTCP parameters in PLAY response,
+ * rather than properly in SDP */
+ if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTCP_INTERVAL,
+ &hval, 0) == GST_RTSP_OK)
+ gst_rtspsrc_handle_rtcp_interval (src, hval);
+ #endif
+
+ gst_rtsp_client_sink_set_state (sink, GST_STATE_PLAYING);
+ sink->state = GST_RTSP_STATE_PLAYING;
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *context = (GstRTSPStreamContext *) walk->data;
+
+ gst_rtsp_stream_unblock_rtcp (context->stream);
+ }
+
+ /* clean up any messages */
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+
+ done:
+ return res;
+
+ create_request_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
+ ("Could not create request. (%s)", str));
+ g_free (str);
+ goto cleanup_error;
+ }
+ send_error:
+ {
+ /* Don't post a message - the rtsp_send method will have
+ * taken care of it because we passed NULL for the response code */
+ goto cleanup_error;
+ }
+ keymgmt_error:
+ {
+ GST_ELEMENT_ERROR (sink, STREAM, DECRYPT_NOKEY, (NULL),
+ ("Could not handle KeyMgmt"));
+ }
+ setup_failed:
+ {
+ GST_ERROR_OBJECT (sink, "setup failed");
+ goto cleanup_error;
+ }
+ cleanup_error:
+ {
+ if (sink->conninfo.connection) {
+ GST_DEBUG_OBJECT (sink, "free connection");
+ gst_rtsp_conninfo_close (sink, &sink->conninfo, TRUE);
+ }
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+ return res;
+ }
+ }
+
+ static GstRTSPResult
+ gst_rtsp_client_sink_pause (GstRTSPClientSink * sink, gboolean async)
+ {
+ GstRTSPResult res = GST_RTSP_OK;
+ GstRTSPMessage request = { 0 };
+ GstRTSPMessage response = { 0 };
+ GList *walk;
+ const gchar *control;
+
+ GST_DEBUG_OBJECT (sink, "PAUSE...");
+
+ if ((res = gst_rtsp_client_sink_ensure_open (sink, async)) < 0)
+ goto open_failed;
+
+ if (!(sink->methods & GST_RTSP_PAUSE))
+ goto not_supported;
+
+ if (sink->state == GST_RTSP_STATE_READY)
+ goto was_paused;
+
+ if (!sink->conninfo.connection || !sink->conninfo.connected)
+ goto no_connection;
+
+ /* construct a control url */
+ control = get_aggregate_control (sink);
+
+ /* loop over the streams. We might exit the loop early when we could do an
+ * aggregate control */
+ for (walk = sink->contexts; walk; walk = g_list_next (walk)) {
+ GstRTSPStreamContext *stream = (GstRTSPStreamContext *) walk->data;
+ GstRTSPConnInfo *info;
+ const gchar *setup_url;
+
+ /* try aggregate control first but do non-aggregate control otherwise */
+ if (control)
+ setup_url = control;
+ else if ((setup_url = stream->conninfo.location) == NULL)
+ continue;
+
+ if (sink->conninfo.connection) {
+ info = &sink->conninfo;
+ } else if (stream->conninfo.connection) {
+ info = &stream->conninfo;
+ } else {
+ continue;
+ }
+
+ if (async)
+ GST_ELEMENT_PROGRESS (sink, CONTINUE, "request",
+ ("Sending PAUSE request"));
+
+ if ((res =
+ gst_rtsp_client_sink_init_request (sink, &request, GST_RTSP_PAUSE,
+ setup_url)) < 0)
+ goto create_request_failed;
+
+ if ((res =
+ gst_rtsp_client_sink_send (sink, info, &request, &response,
+ NULL)) < 0)
+ goto send_error;
+
+ gst_rtsp_message_unset (&request);
+ gst_rtsp_message_unset (&response);
+
+ /* exit early when we did agregate control */
+ if (control)
+ break;
+ }
+
+ /* change element states now */
+ gst_rtsp_client_sink_set_state (sink, GST_STATE_PAUSED);
+
+ no_connection:
+ sink->state = GST_RTSP_STATE_READY;
+
+ done:
+ if (async)
+ gst_rtsp_client_sink_loop_end_cmd (sink, CMD_PAUSE, res);
+
+ return res;
+
+ /* ERRORS */
+ open_failed:
+ {
+ GST_DEBUG_OBJECT (sink, "failed to open stream");
+ goto done;
+ }
+ not_supported:
+ {
+ GST_DEBUG_OBJECT (sink, "PAUSE is not supported");
+ goto done;
+ }
+ was_paused:
+ {
+ GST_DEBUG_OBJECT (sink, "we were already PAUSED");
+ goto done;
+ }
+ create_request_failed:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ GST_ELEMENT_ERROR (sink, LIBRARY, INIT, (NULL),
+ ("Could not create request. (%s)", str));
+ g_free (str);
+ goto done;
+ }
+ send_error:
+ {
+ gchar *str = gst_rtsp_strresult (res);
+
+ gst_rtsp_message_unset (&request);
+ if (res != GST_RTSP_EINTR) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
+ ("Could not send message. (%s)", str));
+ } else {
+ GST_WARNING_OBJECT (sink, "PAUSE interrupted");
+ }
+ g_free (str);
+ goto done;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_handle_message (GstBin * bin, GstMessage * message)
+ {
+ GstRTSPClientSink *rtsp_client_sink;
+
+ rtsp_client_sink = GST_RTSP_CLIENT_SINK (bin);
+
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_ELEMENT:
+ {
+ const GstStructure *s = gst_message_get_structure (message);
+
+ if (gst_structure_has_name (s, "GstUDPSrcTimeout")) {
+ gboolean ignore_timeout;
+
+ GST_DEBUG_OBJECT (bin, "timeout on UDP port");
+
+ GST_OBJECT_LOCK (rtsp_client_sink);
+ ignore_timeout = rtsp_client_sink->ignore_timeout;
+ rtsp_client_sink->ignore_timeout = TRUE;
+ GST_OBJECT_UNLOCK (rtsp_client_sink);
+
+ /* we only act on the first udp timeout message, others are irrelevant
+ * and can be ignored. */
+ if (!ignore_timeout)
+ gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_RECONNECT,
+ CMD_LOOP);
+ /* eat and free */
+ gst_message_unref (message);
+ return;
+ } else if (gst_structure_has_name (s, "GstRTSPStreamBlocking")) {
+ /* An RTSPStream has prerolled */
+ GST_DEBUG_OBJECT (rtsp_client_sink, "received GstRTSPStreamBlocking");
+ g_mutex_lock (&rtsp_client_sink->block_streams_lock);
+ rtsp_client_sink->n_streams_blocked++;
+ g_cond_broadcast (&rtsp_client_sink->block_streams_cond);
+ g_mutex_unlock (&rtsp_client_sink->block_streams_lock);
+ }
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+ break;
+ }
+ case GST_MESSAGE_ASYNC_START:{
+ GstObject *sender;
+
+ sender = GST_MESSAGE_SRC (message);
+
+ GST_LOG_OBJECT (rtsp_client_sink,
+ "Have async-start from %" GST_PTR_FORMAT, sender);
+ if (sender == GST_OBJECT (rtsp_client_sink->internal_bin)) {
+ GST_LOG_OBJECT (rtsp_client_sink, "child bin is now ASYNC");
+ }
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+ break;
+ }
+ case GST_MESSAGE_ASYNC_DONE:
+ {
+ GstObject *sender;
+ gboolean need_async_done;
+
+ sender = GST_MESSAGE_SRC (message);
+ GST_LOG_OBJECT (rtsp_client_sink, "Have async-done from %" GST_PTR_FORMAT,
+ sender);
+
+ g_mutex_lock (&rtsp_client_sink->preroll_lock);
+ if (sender == GST_OBJECT_CAST (rtsp_client_sink->internal_bin)) {
+ GST_LOG_OBJECT (rtsp_client_sink, "child bin is no longer ASYNC");
+ }
+ need_async_done = rtsp_client_sink->in_async;
+ if (rtsp_client_sink->in_async) {
+ rtsp_client_sink->in_async = FALSE;
+ g_cond_broadcast (&rtsp_client_sink->preroll_cond);
+ }
+ g_mutex_unlock (&rtsp_client_sink->preroll_lock);
+
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+
+ if (need_async_done) {
+ GST_DEBUG_OBJECT (rtsp_client_sink, "Posting ASYNC-DONE");
+ gst_element_post_message (GST_ELEMENT_CAST (rtsp_client_sink),
+ gst_message_new_async_done (GST_OBJECT_CAST (rtsp_client_sink),
+ GST_CLOCK_TIME_NONE));
+ }
+ break;
+ }
+ case GST_MESSAGE_ERROR:
+ {
+ GstObject *sender;
+
+ sender = GST_MESSAGE_SRC (message);
+
+ GST_DEBUG_OBJECT (rtsp_client_sink, "got error from %s",
+ GST_ELEMENT_NAME (sender));
+
+ /* FIXME: Ignore errors on RTCP? */
+ /* fatal but not our message, forward */
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+ break;
+ }
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ if (GST_MESSAGE_SRC (message) ==
+ (GstObject *) rtsp_client_sink->internal_bin) {
+ GstState newstate, pending;
+ gst_message_parse_state_changed (message, NULL, &newstate, &pending);
+ g_mutex_lock (&rtsp_client_sink->preroll_lock);
+ rtsp_client_sink->prerolled = (newstate >= GST_STATE_PAUSED)
+ && pending == GST_STATE_VOID_PENDING;
+ g_cond_broadcast (&rtsp_client_sink->preroll_cond);
+ g_mutex_unlock (&rtsp_client_sink->preroll_lock);
+ GST_DEBUG_OBJECT (bin,
+ "Internal bin changed state to %s (pending %s). Prerolled now %d",
+ gst_element_state_get_name (newstate),
+ gst_element_state_get_name (pending), rtsp_client_sink->prerolled);
+ }
+ /* fallthrough */
+ }
+ default:
+ {
+ GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+ break;
+ }
+ }
+ }
+
+ /* the thread where everything happens */
+ static void
+ gst_rtsp_client_sink_thread (GstRTSPClientSink * sink)
+ {
+ gint cmd;
+
+ GST_OBJECT_LOCK (sink);
+ cmd = sink->pending_cmd;
+ if (cmd == CMD_RECONNECT || cmd == CMD_RECORD || cmd == CMD_PAUSE
+ || cmd == CMD_LOOP || cmd == CMD_OPEN)
+ sink->pending_cmd = CMD_LOOP;
+ else
+ sink->pending_cmd = CMD_WAIT;
+ GST_DEBUG_OBJECT (sink, "got command %s", cmd_to_string (cmd));
+
+ /* we got the message command, so ensure communication is possible again */
+ gst_rtsp_client_sink_connection_flush (sink, FALSE);
+
+ sink->busy_cmd = cmd;
+ GST_OBJECT_UNLOCK (sink);
+
+ switch (cmd) {
+ case CMD_OPEN:
+ if (gst_rtsp_client_sink_open (sink, TRUE) == GST_RTSP_ERROR)
+ gst_rtsp_client_sink_loop_send_cmd (sink, CMD_WAIT,
+ CMD_ALL & ~CMD_CLOSE);
+ break;
+ case CMD_RECORD:
+ gst_rtsp_client_sink_record (sink, TRUE);
+ break;
+ case CMD_PAUSE:
+ gst_rtsp_client_sink_pause (sink, TRUE);
+ break;
+ case CMD_CLOSE:
+ gst_rtsp_client_sink_close (sink, TRUE, FALSE);
+ break;
+ case CMD_LOOP:
+ gst_rtsp_client_sink_loop (sink);
+ break;
+ case CMD_RECONNECT:
+ gst_rtsp_client_sink_reconnect (sink, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ GST_OBJECT_LOCK (sink);
+ /* and go back to sleep */
+ if (sink->pending_cmd == CMD_WAIT) {
+ if (sink->task)
+ gst_task_pause (sink->task);
+ }
+ /* reset waiting */
+ sink->busy_cmd = CMD_WAIT;
+ GST_OBJECT_UNLOCK (sink);
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_start (GstRTSPClientSink * sink)
+ {
+ GST_DEBUG_OBJECT (sink, "starting");
+
+ sink->streams_collected = FALSE;
+ gst_element_set_locked_state (GST_ELEMENT (sink->internal_bin), TRUE);
+
+ gst_rtsp_client_sink_set_state (sink, GST_STATE_READY);
+
+ GST_OBJECT_LOCK (sink);
+ sink->pending_cmd = CMD_WAIT;
+
+ if (sink->task == NULL) {
+ sink->task =
+ gst_task_new ((GstTaskFunction) gst_rtsp_client_sink_thread, sink,
+ NULL);
+ if (sink->task == NULL)
+ goto task_error;
+
+ gst_task_set_lock (sink->task, GST_RTSP_STREAM_GET_LOCK (sink));
+ }
+ GST_OBJECT_UNLOCK (sink);
+
+ return TRUE;
+
+ /* ERRORS */
+ task_error:
+ {
+ GST_OBJECT_UNLOCK (sink);
+ GST_ERROR_OBJECT (sink, "failed to create task");
+ return FALSE;
+ }
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_stop (GstRTSPClientSink * sink)
+ {
+ GstTask *task;
+
+ GST_DEBUG_OBJECT (sink, "stopping");
+
+ /* also cancels pending task */
+ gst_rtsp_client_sink_loop_send_cmd (sink, CMD_WAIT, CMD_ALL & ~CMD_CLOSE);
+
+ GST_OBJECT_LOCK (sink);
+ if ((task = sink->task)) {
+ sink->task = NULL;
+ GST_OBJECT_UNLOCK (sink);
+
+ gst_task_stop (task);
+
+ g_mutex_lock (&sink->block_streams_lock);
+ g_cond_broadcast (&sink->block_streams_cond);
+ g_mutex_unlock (&sink->block_streams_lock);
+
+ /* make sure it is not running */
+ GST_RTSP_STREAM_LOCK (sink);
+ GST_RTSP_STREAM_UNLOCK (sink);
+
+ /* now wait for the task to finish */
+ gst_task_join (task);
+
+ /* and free the task */
+ gst_object_unref (GST_OBJECT (task));
+
+ GST_OBJECT_LOCK (sink);
+ }
+ GST_OBJECT_UNLOCK (sink);
+
+ /* ensure synchronously all is closed and clean */
+ gst_rtsp_client_sink_close (sink, FALSE, TRUE);
+
+ return TRUE;
+ }
+
+ static GstStateChangeReturn
+ gst_rtsp_client_sink_change_state (GstElement * element,
+ GstStateChange transition)
+ {
+ GstRTSPClientSink *rtsp_client_sink;
+ GstStateChangeReturn ret;
+
+ rtsp_client_sink = GST_RTSP_CLIENT_SINK (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_rtsp_client_sink_start (rtsp_client_sink))
+ goto start_failed;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ /* init some state */
+ rtsp_client_sink->cur_protocols = rtsp_client_sink->protocols;
+ /* first attempt, don't ignore timeouts */
+ rtsp_client_sink->ignore_timeout = FALSE;
+ rtsp_client_sink->open_error = FALSE;
+
+ gst_rtsp_client_sink_set_state (rtsp_client_sink, GST_STATE_PAUSED);
+
+ g_mutex_lock (&rtsp_client_sink->preroll_lock);
+ if (rtsp_client_sink->in_async) {
+ GST_DEBUG_OBJECT (rtsp_client_sink, "Posting ASYNC-START");
+ gst_element_post_message (GST_ELEMENT_CAST (rtsp_client_sink),
+ gst_message_new_async_start (GST_OBJECT_CAST (rtsp_client_sink)));
+ }
+ g_mutex_unlock (&rtsp_client_sink->preroll_lock);
+
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ /* fall-through */
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ /* unblock the tcp tasks and make the loop waiting */
+ if (gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_WAIT,
+ CMD_LOOP)) {
+ /* make sure it is waiting before we send PLAY below */
+ GST_RTSP_STREAM_LOCK (rtsp_client_sink);
+ GST_RTSP_STREAM_UNLOCK (rtsp_client_sink);
+ }
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ gst_rtsp_client_sink_set_state (rtsp_client_sink, GST_STATE_READY);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto done;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ ret = GST_STATE_CHANGE_SUCCESS;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ /* Return ASYNC and preroll input streams */
+ g_mutex_lock (&rtsp_client_sink->preroll_lock);
+ if (rtsp_client_sink->in_async)
+ ret = GST_STATE_CHANGE_ASYNC;
+ g_mutex_unlock (&rtsp_client_sink->preroll_lock);
+ gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_OPEN, 0);
+
+ /* CMD_OPEN has been scheduled. Wait until the sink thread starts
+ * opening connection to the server */
+ g_mutex_lock (&rtsp_client_sink->open_conn_lock);
+ while (!rtsp_client_sink->open_conn_start) {
+ GST_DEBUG_OBJECT (rtsp_client_sink,
+ "wait for connection to be started");
+ g_cond_wait (&rtsp_client_sink->open_conn_cond,
+ &rtsp_client_sink->open_conn_lock);
+ }
+ rtsp_client_sink->open_conn_start = FALSE;
+ g_mutex_unlock (&rtsp_client_sink->open_conn_lock);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{
+ GST_DEBUG_OBJECT (rtsp_client_sink,
+ "Switching to playing -sending RECORD");
+ gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_RECORD, 0);
+ ret = GST_STATE_CHANGE_SUCCESS;
+ break;
+ }
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ /* send pause request and keep the idle task around */
+ gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_PAUSE,
+ CMD_LOOP);
+ ret = GST_STATE_CHANGE_NO_PREROLL;
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ gst_rtsp_client_sink_loop_send_cmd (rtsp_client_sink, CMD_CLOSE,
+ CMD_PAUSE);
+ ret = GST_STATE_CHANGE_SUCCESS;
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_rtsp_client_sink_stop (rtsp_client_sink);
+ ret = GST_STATE_CHANGE_SUCCESS;
+ break;
+ default:
+ break;
+ }
+
+ done:
+ return ret;
+
+ start_failed:
+ {
+ GST_DEBUG_OBJECT (rtsp_client_sink, "start failed");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ }
+
+ /*** GSTURIHANDLER INTERFACE *************************************************/
+
+ static GstURIType
+ gst_rtsp_client_sink_uri_get_type (GType type)
+ {
+ return GST_URI_SINK;
+ }
+
+ static const gchar *const *
+ gst_rtsp_client_sink_uri_get_protocols (GType type)
+ {
+ static const gchar *protocols[] =
+ { "rtsp", "rtspu", "rtspt", "rtsph", "rtsp-sdp",
+ "rtsps", "rtspsu", "rtspst", "rtspsh", NULL
+ };
+
+ return protocols;
+ }
+
+ static gchar *
+ gst_rtsp_client_sink_uri_get_uri (GstURIHandler * handler)
+ {
+ GstRTSPClientSink *sink = GST_RTSP_CLIENT_SINK (handler);
+
+ /* FIXME: make thread-safe */
+ return g_strdup (sink->conninfo.location);
+ }
+
+ static gboolean
+ gst_rtsp_client_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri,
+ GError ** error)
+ {
+ GstRTSPClientSink *sink;
+ GstRTSPResult res;
+ GstSDPResult sres;
+ GstRTSPUrl *newurl = NULL;
+ GstSDPMessage *sdp = NULL;
+
+ sink = GST_RTSP_CLIENT_SINK (handler);
+
+ /* same URI, we're fine */
+ if (sink->conninfo.location && uri && !strcmp (uri, sink->conninfo.location))
+ goto was_ok;
+
+ if (g_str_has_prefix (uri, "rtsp-sdp://")) {
+ sres = gst_sdp_message_new (&sdp);
+ if (sres < 0)
+ goto sdp_failed;
+
+ GST_DEBUG_OBJECT (sink, "parsing SDP message");
+ sres = gst_sdp_message_parse_uri (uri, sdp);
+ if (sres < 0)
+ goto invalid_sdp;
+ } else {
+ /* try to parse */
+ GST_DEBUG_OBJECT (sink, "parsing URI");
+ if ((res = gst_rtsp_url_parse (uri, &newurl)) < 0)
+ goto parse_error;
+ }
+
+ /* if worked, free previous and store new url object along with the original
+ * location. */
+ GST_DEBUG_OBJECT (sink, "configuring URI");
+ g_free (sink->conninfo.location);
+ sink->conninfo.location = g_strdup (uri);
+ gst_rtsp_url_free (sink->conninfo.url);
+ sink->conninfo.url = newurl;
+ g_free (sink->conninfo.url_str);
+ if (newurl)
+ sink->conninfo.url_str = gst_rtsp_url_get_request_uri (sink->conninfo.url);
+ else
+ sink->conninfo.url_str = NULL;
+
+ if (sink->uri_sdp)
+ gst_sdp_message_free (sink->uri_sdp);
+ sink->uri_sdp = sdp;
+ sink->from_sdp = sdp != NULL;
+
+ GST_DEBUG_OBJECT (sink, "set uri: %s", GST_STR_NULL (uri));
+ GST_DEBUG_OBJECT (sink, "request uri is: %s",
+ GST_STR_NULL (sink->conninfo.url_str));
+
+ return TRUE;
+
+ /* Special cases */
+ was_ok:
+ {
+ GST_DEBUG_OBJECT (sink, "URI was ok: '%s'", GST_STR_NULL (uri));
+ return TRUE;
+ }
+ sdp_failed:
+ {
+ GST_ERROR_OBJECT (sink, "Could not create new SDP (%d)", sres);
+ g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+ "Could not create SDP");
+ return FALSE;
+ }
+ invalid_sdp:
+ {
+ GST_ERROR_OBJECT (sink, "Not a valid SDP (%d) '%s'", sres,
+ GST_STR_NULL (uri));
+ gst_sdp_message_free (sdp);
+ g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+ "Invalid SDP");
+ return FALSE;
+ }
+ parse_error:
+ {
+ GST_ERROR_OBJECT (sink, "Not a valid RTSP url '%s' (%d)",
+ GST_STR_NULL (uri), res);
+ g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+ "Invalid RTSP URI");
+ return FALSE;
+ }
+ }
+
+ static void
+ gst_rtsp_client_sink_uri_handler_init (gpointer g_iface, gpointer iface_data)
+ {
+ GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+
+ iface->get_type = gst_rtsp_client_sink_uri_get_type;
+ iface->get_protocols = gst_rtsp_client_sink_uri_get_protocols;
+ iface->get_uri = gst_rtsp_client_sink_uri_get_uri;
+ iface->set_uri = gst_rtsp_client_sink_uri_set_uri;
+ }