Merge branch 'move_subdir_rtsp-server' into tizen_gst_1.19.2_mono
authorGilbok Lee <gilbok.lee@samsung.com>
Tue, 25 Jan 2022 04:34:47 +0000 (13:34 +0900)
committerGilbok Lee <gilbok.lee@samsung.com>
Tue, 25 Jan 2022 04:34:47 +0000 (13:34 +0900)
Change-Id: I15c0ddc678464e2c8b225170f51f9c0ff3aa10d0

28 files changed:
1  2 
subprojects/gst-rtsp-server/.gitlab-ci.yml
subprojects/gst-rtsp-server/NEWS
subprojects/gst-rtsp-server/gst-rtsp-server.doap
subprojects/gst-rtsp-server/gst/rtsp-server/gstwfdmessage-ext.c
subprojects/gst-rtsp-server/gst/rtsp-server/gstwfdmessage-ext.h
subprojects/gst-rtsp-server/gst/rtsp-server/gstwfdmessage.c
subprojects/gst-rtsp-server/gst/rtsp-server/gstwfdmessage.h
subprojects/gst-rtsp-server/gst/rtsp-server/meson.build
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client-ext.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client-ext.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client-wfd.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client-wfd.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-client.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-ext.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-ext.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-uri.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-wfd.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media-factory-wfd.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-media.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-object.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-wfd.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server-wfd.h
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-server.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.c
subprojects/gst-rtsp-server/gst/rtsp-server/rtsp-stream.h
subprojects/gst-rtsp-server/gst/rtsp-sink/gstrtspclientsink.c

index 0000000000000000000000000000000000000000,c61aa7a52913e444ef5b02bbc7ed1777ed8bce3c..bed438dd171e07d5ab1275c1f1645a5c1357bff0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1 +1,1 @@@
 -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"
index 0000000000000000000000000000000000000000,0e581c39b8f309e72086551ee7ea1045409aa9da..0e581c39b8f309e72086551ee7ea1045409aa9da
mode 000000,100644..100755
--- /dev/null
index 0000000000000000000000000000000000000000,6d323ac90d6a3e83c7b812f528b372369aa1ca3b..02b25fd0ed45618e3cb62132bafe6ea4ef8d9644
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,500 +1,520 @@@
+ <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>
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..73ce8b104d3f5e5dd27a0afdc75e4c26a884f626
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,520 @@@
++/* 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;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..02f323cf39bc1473b193664bf8e8b9da03ae2f8d
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,153 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ee8ccce655c16a65a7c0f0f6267be271a6de115e
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2955 @@@
++/* 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, &copy) == 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;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..48d587a720bc7e8537feef9009b13295609319a5
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,852 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,24d7c39adbebe42b5c82836fa325e888c3e0a486..f005bf5df2d703fc43bb41ec97b5b15c147971d0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,99 +1,113 @@@
+ 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)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3f43973d816363b60f383df0532c43e655dd193f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,917 @@@
++/* 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;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..eb7315410c26c82025724cd2c090575988dc7531
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,75 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a7e492be0d5ad0fa60ab00069350f1c6340ab0d5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4373 @@@
++/* 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;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..dd3c049c40bece4f5ec4194cad8eb314cbc71965
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,396 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,e5a62c0cd991522b4d7c8e732eda9fed6650aa15..5a2085b08502428e522bdbb963a421abca58b288
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,5404 +1,5447 @@@
 -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);
++  }
++}
index 0000000000000000000000000000000000000000,604a042399238fbc52f5646513484d86b501048f..746fab2ba2617d71062cda603ba3e794ca1eefe6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,294 +1,301 @@@
+ /* 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__ */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a5d60d04e12c41d4603a1d1c79496a66bccd63d7
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,780 @@@
++/* 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);
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1cca81f5e6f5806fac18d9a8b8b5d6a9f6dade73
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,128 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,50089fc9cbdf1f6e5ebd140b0d24426a5c457ae6..22befcc47dbd5beea84cacf5b4db9114345f010b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,646 +1,647 @@@
+ /* 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;
+   }
+ }
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b6e087c943054b6a9e102c14544410a5eddb4e4a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2087 @@@
++/* 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;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..069090811a9cc5e35e89e55eaf9e056030c97446
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,175 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,f2d498ac2ffd700efd2a3611cdb4c2558dab1c79..bf2261502a4ab5b1e355ac8f4816a6accf7f566f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,5195 +1,5269 @@@
 -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, &current_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;
++}
index 0000000000000000000000000000000000000000,9c2494a64e3a75275175215ed7b500326ae3d974..d2d2c99907fa5f077a8442b27313fdd9d6bf7931
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,449 +1,465 @@@
+ /* 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__ */
index 0000000000000000000000000000000000000000,4f44f3a5008b95894fed5bb5042890940c31dbce..44f905552b74c43e8b8e89518c4e6a9c20c8ee12
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,211 +1,212 @@@
+ /* 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__ */
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6e03395107e85693803ef479248f32cd14f38095
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,495 @@@
++/* 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;
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ea2d31927fdb1cf06f4ac32212f2a89a3f191dfa
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,186 @@@
++/* 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__ */
index 0000000000000000000000000000000000000000,9d3cb584dc8a70bb320fe06a1daccaf1f6432840..eaf590862d3d129755231e6852e94021328999e2
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1520 +1,1525 @@@
 -  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;
+ }
index 0000000000000000000000000000000000000000,92ef3587978b227568cf4489050b9bb7d4bc35c4..6e52f5874f5df28f7fcb7f1432052f27e8444c00
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,6366 +1,6393 @@@
+ /* 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);
+ }
index 0000000000000000000000000000000000000000,5e6ff2151a39a51eb2232e7405b3394b0f6c73fb..db9d5cf79787d1a24ab3d831a8cfd3c07a0a7571
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,406 +1,409 @@@
+ /* 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__ */
index 0000000000000000000000000000000000000000,3573e2088b4a1638b85796503e3748e297e20274..1bc9931a62b69497aed10622bbf777b8b0278437
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,5251 +1,5253 @@@
 -  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;
+ }