mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 16:26:39 +00:00
889a3fe932
This is consistent with the librtmp-based old rtmp plugin and ffmpeg. While some servers require a valid flash-version, others are failing with a too long or any flash-version at all. By changing to the same default as in the old plugin and in ffmpeg, GStreamer will at least behave the same and will work and fail with the same servers without setting a flash-version. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5293>
299 lines
10 KiB
C
299 lines
10 KiB
C
/* GStreamer
|
|
* Copyright (C) 2017 Make.TV, Inc. <info@make.tv>
|
|
* Contact: Jan Alexander Steffens (heftig) <jsteffens@make.tv>
|
|
*
|
|
* 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 "gstrtmp2locationhandler.h"
|
|
#include "rtmp/rtmputils.h"
|
|
#include "rtmp/rtmpclient.h"
|
|
#include <string.h>
|
|
|
|
#define DEFAULT_SCHEME GST_RTMP_SCHEME_RTMP
|
|
#define DEFAULT_HOST "localhost"
|
|
#define DEFAULT_APPLICATION "live"
|
|
#define DEFAULT_STREAM "myStream"
|
|
#define DEFAULT_LOCATION "rtmp://" DEFAULT_HOST "/" DEFAULT_APPLICATION "/" DEFAULT_STREAM
|
|
#define DEFAULT_SECURE_TOKEN NULL
|
|
#define DEFAULT_USERNAME NULL
|
|
#define DEFAULT_PASSWORD NULL
|
|
#define DEFAULT_AUTHMOD GST_RTMP_AUTHMOD_AUTO
|
|
#define DEFAULT_TIMEOUT 5
|
|
#define DEFAULT_FLASH_VERSION NULL
|
|
|
|
G_DEFINE_INTERFACE (GstRtmpLocationHandler, gst_rtmp_location_handler, 0);
|
|
|
|
#define GST_CAT_DEFAULT gst_rtmp_location_handler_debug_category
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
static void
|
|
gst_rtmp_location_handler_default_init (GstRtmpLocationHandlerInterface * iface)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "rtmp2locationhandler", 0,
|
|
"RTMP2 Location Handling");
|
|
|
|
g_object_interface_install_property (iface, g_param_spec_string ("location",
|
|
"Location", "Location of RTMP stream to access", DEFAULT_LOCATION,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_enum ("scheme",
|
|
"Scheme", "RTMP connection scheme",
|
|
GST_TYPE_RTMP_SCHEME, DEFAULT_SCHEME,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_string ("host",
|
|
"Host", "RTMP server host name", DEFAULT_HOST,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_int ("port", "Port",
|
|
"RTMP server port", 1, 65535,
|
|
gst_rtmp_scheme_get_default_port (DEFAULT_SCHEME),
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("application", "Application",
|
|
"RTMP application path", DEFAULT_APPLICATION,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_string ("stream",
|
|
"Stream", "RTMP stream path", DEFAULT_STREAM,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_string ("username",
|
|
"User name", "RTMP authorization user name", DEFAULT_USERNAME,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_string ("password",
|
|
"Password", "RTMP authorization password", DEFAULT_PASSWORD,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("secure-token", "Secure token",
|
|
"RTMP authorization token", DEFAULT_SECURE_TOKEN,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_enum ("authmod",
|
|
"Authorization mode", "RTMP authorization mode",
|
|
GST_TYPE_RTMP_AUTHMOD, DEFAULT_AUTHMOD,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface, g_param_spec_uint ("timeout",
|
|
"Timeout", "RTMP timeout in seconds", 0, G_MAXUINT, DEFAULT_TIMEOUT,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
/**
|
|
* GstRtmpLocationHandler::tls-validation-flags:
|
|
*
|
|
* TLS certificate validation flags used to validate server
|
|
* certificate.
|
|
*
|
|
* GLib guarantees that if certificate verification fails, at least one
|
|
* error will be set, but it does not guarantee that all possible errors
|
|
* will be set. Accordingly, you may not safely decide to ignore any
|
|
* particular type of error.
|
|
*
|
|
* For example, it would be incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if
|
|
* you want to allow expired certificates, because this could potentially be
|
|
* the only error flag set even if other problems exist with the
|
|
* certificate.
|
|
*/
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_flags ("tls-validation-flags", "TLS validation flags",
|
|
"TLS validation flags to use", G_TYPE_TLS_CERTIFICATE_FLAGS,
|
|
G_TLS_CERTIFICATE_VALIDATE_ALL,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_interface_install_property (iface,
|
|
g_param_spec_string ("flash-version", "Flash version",
|
|
"Flash version reported to the server", DEFAULT_FLASH_VERSION,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static GstURIType
|
|
uri_handler_get_type_sink (GType type)
|
|
{
|
|
return GST_URI_SINK;
|
|
}
|
|
|
|
static GstURIType
|
|
uri_handler_get_type_src (GType type)
|
|
{
|
|
return GST_URI_SRC;
|
|
}
|
|
|
|
static const gchar *const *
|
|
uri_handler_get_protocols (GType type)
|
|
{
|
|
return gst_rtmp_scheme_get_strings ();
|
|
}
|
|
|
|
static gchar *
|
|
uri_handler_get_uri (GstURIHandler * handler)
|
|
{
|
|
GstRtmpLocation location = { 0, };
|
|
gchar *string;
|
|
|
|
g_object_get (handler, "scheme", &location.scheme, "host", &location.host,
|
|
"port", &location.port, "application", &location.application,
|
|
"stream", &location.stream, NULL);
|
|
|
|
string = gst_rtmp_location_get_string (&location, TRUE);
|
|
gst_rtmp_location_clear (&location);
|
|
return string;
|
|
}
|
|
|
|
static gboolean
|
|
uri_handler_set_uri (GstURIHandler * handler, const gchar * string,
|
|
GError ** error)
|
|
{
|
|
GstRtmpLocationHandler *self = GST_RTMP_LOCATION_HANDLER (handler);
|
|
GstUri *uri;
|
|
const gchar *scheme_sep, *path_sep, *stream_sep, *host, *userinfo;
|
|
GstRtmpScheme scheme;
|
|
guint port;
|
|
gboolean ret = FALSE;
|
|
|
|
GST_DEBUG_OBJECT (self, "setting URI from %s", GST_STR_NULL (string));
|
|
g_return_val_if_fail (string, FALSE);
|
|
|
|
scheme_sep = strstr (string, "://");
|
|
if (!scheme_sep) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
|
|
"URI lacks scheme: %s", string);
|
|
return FALSE;
|
|
}
|
|
|
|
path_sep = strchr (scheme_sep + 3, '/');
|
|
if (!path_sep) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
|
|
"URI lacks path: %s", string);
|
|
return FALSE;
|
|
}
|
|
|
|
stream_sep = strrchr (path_sep + 1, '/');
|
|
if (!stream_sep) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
|
|
"URI lacks stream: %s", string);
|
|
return FALSE;
|
|
}
|
|
|
|
{
|
|
gchar *string_without_path = g_strndup (string, path_sep - string);
|
|
uri = gst_uri_from_string_escaped (string_without_path);
|
|
g_free (string_without_path);
|
|
}
|
|
|
|
if (!uri) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
|
"URI failed to parse: %s", string);
|
|
return FALSE;
|
|
}
|
|
|
|
gst_uri_normalize (uri);
|
|
|
|
scheme = gst_rtmp_scheme_from_uri (uri);
|
|
if (scheme < 0) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
|
|
"URI has bad scheme: %s", string);
|
|
goto out;
|
|
}
|
|
|
|
host = gst_uri_get_host (uri);
|
|
if (!host) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
|
|
"URI lacks hostname: %s", string);
|
|
goto out;
|
|
}
|
|
|
|
port = gst_uri_get_port (uri);
|
|
if (port == GST_URI_NO_PORT) {
|
|
port = gst_rtmp_scheme_get_default_port (scheme);
|
|
}
|
|
|
|
{
|
|
const gchar *path = path_sep + 1, *stream = stream_sep + 1;
|
|
gchar *application = g_strndup (path, stream_sep - path);
|
|
|
|
GST_DEBUG_OBJECT (self, "setting location to %s://%s:%u/%s stream %s",
|
|
gst_rtmp_scheme_to_string (scheme), host, port, application, stream);
|
|
|
|
g_object_set (self, "scheme", scheme, "host", host, "port", port,
|
|
"application", application, "stream", stream, "username", NULL,
|
|
"password", NULL, NULL);
|
|
|
|
g_free (application);
|
|
}
|
|
|
|
userinfo = gst_uri_get_userinfo (uri);
|
|
if (userinfo) {
|
|
gchar *user, *pass;
|
|
gchar **split = g_strsplit (userinfo, ":", 2);
|
|
|
|
if (!split || !split[0] || !split[1]) {
|
|
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_REFERENCE,
|
|
"Failed to parse username:password data");
|
|
g_strfreev (split);
|
|
goto out;
|
|
}
|
|
|
|
if (g_strrstr (split[1], ":") != NULL)
|
|
GST_WARNING_OBJECT (self, "userinfo %s contains more than one ':', will "
|
|
"assume that the first ':' delineates user:pass. You should escape "
|
|
"the user and pass before adding to the URI.", userinfo);
|
|
|
|
user = g_uri_unescape_string (split[0], NULL);
|
|
pass = g_uri_unescape_string (split[1], NULL);
|
|
g_strfreev (split);
|
|
|
|
g_object_set (self, "username", user, "password", pass, NULL);
|
|
g_free (user);
|
|
g_free (pass);
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
gst_uri_unref (uri);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
gst_rtmp_location_handler_implement_uri_handler (GstURIHandlerInterface * iface,
|
|
GstURIType type)
|
|
{
|
|
switch (type) {
|
|
case GST_URI_SINK:
|
|
iface->get_type = uri_handler_get_type_sink;
|
|
break;
|
|
case GST_URI_SRC:
|
|
iface->get_type = uri_handler_get_type_src;
|
|
break;
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
iface->get_protocols = uri_handler_get_protocols;
|
|
iface->get_uri = uri_handler_get_uri;
|
|
iface->set_uri = uri_handler_set_uri;
|
|
}
|
|
|
|
gboolean
|
|
gst_rtmp_location_handler_set_uri (GstRtmpLocationHandler * handler,
|
|
const gchar * uri)
|
|
{
|
|
GError *error = NULL;
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (GST_IS_RTMP_LOCATION_HANDLER (handler), FALSE);
|
|
|
|
ret = gst_uri_handler_set_uri (GST_URI_HANDLER (handler), uri, &error);
|
|
if (!ret) {
|
|
GST_ERROR_OBJECT (handler, "Failed to set URI: %s", error->message);
|
|
g_object_set (handler, "scheme", DEFAULT_SCHEME, "host", NULL,
|
|
"port", gst_rtmp_scheme_get_default_port (DEFAULT_SCHEME),
|
|
"application", NULL, "stream", NULL, NULL);
|
|
g_error_free (error);
|
|
}
|
|
return ret;
|
|
}
|