mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
Add files from gst-rtmp
For master, without autotools.
This commit is contained in:
parent
5320bb9085
commit
2386858a91
25 changed files with 8143 additions and 4 deletions
|
@ -8,10 +8,11 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux',
|
|||
'ivfparse', 'ivtc', 'jp2kdecimator', 'jpegformat', 'librfb',
|
||||
'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux',
|
||||
'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy',
|
||||
'rawparse', 'removesilence', 'rist', 'rtp', 'sdp', 'segmentclip',
|
||||
'siren', 'smooth', 'speed', 'subenc', 'timecode', 'transcode',
|
||||
'videofilters', 'videoframe_audiolevel', 'videoparsers',
|
||||
'videosignal', 'vmnc', 'y4m', 'yadif']
|
||||
'rawparse', 'removesilence', 'rist', 'rtmp2', 'rtp', 'sdp',
|
||||
'segmentclip', 'siren', 'smooth', 'speed', 'subenc',
|
||||
'timecode', 'transcode', 'videofilters',
|
||||
'videoframe_audiolevel', 'videoparsers', 'videosignal',
|
||||
'vmnc', 'y4m', 'yadif']
|
||||
if not get_option(plugin).disabled()
|
||||
subdir(plugin)
|
||||
endif
|
||||
|
|
63
gst/rtmp2/TODO
Normal file
63
gst/rtmp2/TODO
Normal file
|
@ -0,0 +1,63 @@
|
|||
- rtmp2sink: Should look into reconnecting and resuming stream without
|
||||
deleting and recreating stream, which drops clients.
|
||||
|
||||
- Move AMF parser/serializer to GstRtmpMeta?
|
||||
- Move AMF nodes from g_slice to GstMiniObject?
|
||||
|
||||
- First video frame that comes from Wowza seems to be out-of-order; librtmp
|
||||
does not have this problem
|
||||
|
||||
- Refactor connection, pull out the ad-hoc read and write handling and put it
|
||||
with the chunk layer into GBuffered{In,Out}putStream subclasses
|
||||
|
||||
- Refactor elements and pull out the common connection+mainloop handling code
|
||||
into a context object
|
||||
|
||||
- Change the location properties into something with less boilerplate?
|
||||
|
||||
Perhaps a GstStructure-based prop, custom GValue transforms or GstValue
|
||||
(de)serializing
|
||||
|
||||
- Use glib-mkenums to generate GEnumClasses
|
||||
|
||||
- Post-connect onStatus handling (needed for src EOS and async errors?)
|
||||
|
||||
- Better mux/demux, at the cost of losing compatibility with flvmux/demux.
|
||||
|
||||
Something like (a/x = application/x-rtmp-messages):
|
||||
|
||||
rtmp2src ! a/x ! rtmp2demux ! a/x,type=video ! rtmp2videodecode ! h264parse
|
||||
! a/x,type=audio ! rtmp2audiodecode ! aacparse
|
||||
|
||||
x264enc ! rtmp2videoencode ! a/x,type=video ! rtmp2mux ! a/x ! rtmp2sink
|
||||
fdkaacenc ! rtmp2audioencode ! a/x,type=audio !
|
||||
|
||||
And also, in case no muxing is required:
|
||||
|
||||
x264enc ! rtmp2videoencode ! a/x,type=video ! rtmp2sink
|
||||
fdkaacenc ! rtmp2audioencode ! a/x,type=video ! rtmp2sink
|
||||
|
||||
Proper GstBuffer timestamps need proper timestamp wraparound handling
|
||||
|
||||
- Better client element, which generalizes the existing sink/src to allow
|
||||
multiple streams over one connection
|
||||
- Request src pad to play a stream
|
||||
- Request sink pad to publish a stream (base it on GstAggregator?)
|
||||
- rtmp2sink/src just specialize the client element with a static pad
|
||||
|
||||
- Server implementation
|
||||
|
||||
- Support more protocols
|
||||
- rtmpe (App-layer encryption)
|
||||
- rtmpt (HTTP tunneling)
|
||||
- rtmpte (HTTP tunneling + App-layer encryption)
|
||||
- rtmpts (HTTPS tunneling)
|
||||
- rtmfp (UDP)
|
||||
|
||||
Needed testing:
|
||||
|
||||
- AMF parsing
|
||||
|
||||
- connection closure by peer
|
||||
|
||||
- connection timeouts
|
44
gst/rtmp2/gstrtmp2.c
Normal file
44
gst/rtmp2/gstrtmp2.c
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2014 David Schleef <ds@schleef.org>
|
||||
* 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 Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstrtmp2src.h"
|
||||
#include "gstrtmp2sink.h"
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gst_element_register (plugin, "rtmp2src", GST_RANK_PRIMARY + 1,
|
||||
GST_TYPE_RTMP2_SRC);
|
||||
gst_element_register (plugin, "rtmp2sink", GST_RANK_PRIMARY + 1,
|
||||
GST_TYPE_RTMP2_SINK);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
rtmp2,
|
||||
"RTMP plugin",
|
||||
plugin_init, VERSION, GST_LICENSE, PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
267
gst/rtmp2/gstrtmp2locationhandler.c
Normal file
267
gst/rtmp2/gstrtmp2locationhandler.c
Normal file
|
@ -0,0 +1,267 @@
|
|||
/* 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
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
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 (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 **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;
|
||||
}
|
||||
|
||||
g_object_set (self, "username", split[0], "password", split[1], NULL);
|
||||
g_strfreev (split);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
49
gst/rtmp2/gstrtmp2locationhandler.h
Normal file
49
gst/rtmp2/gstrtmp2locationhandler.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
#ifndef __GST_RTMP_LOCATION_HANDLER_H__
|
||||
#define __GST_RTMP_LOCATION_HANDLER_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "rtmp/rtmpclient.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
#define GST_TYPE_RTMP_LOCATION_HANDLER (gst_rtmp_location_handler_get_type ())
|
||||
#define GST_RTMP_LOCATION_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTMP_LOCATION_HANDLER, GstRtmpLocationHandler))
|
||||
#define GST_IS_RTMP_LOCATION_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTMP_LOCATION_HANDLER))
|
||||
#define GST_RTMP_LOCATION_HANDLER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_RTMP_LOCATION_HANDLER, GstRtmpLocationHandlerInterface))
|
||||
typedef struct _GstRtmpLocationHandler GstRtmpLocationHandler; /* dummy object */
|
||||
typedef struct _GstRtmpLocationHandlerInterface GstRtmpLocationHandlerInterface;
|
||||
|
||||
struct _GstRtmpLocationHandlerInterface
|
||||
{
|
||||
GTypeInterface parent_iface;
|
||||
};
|
||||
|
||||
GType gst_rtmp_location_handler_get_type (void);
|
||||
|
||||
void gst_rtmp_location_handler_implement_uri_handler (GstURIHandlerInterface *
|
||||
iface, GstURIType type);
|
||||
|
||||
gboolean gst_rtmp_location_handler_set_uri (GstRtmpLocationHandler * handler,
|
||||
const gchar * uri);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
1064
gst/rtmp2/gstrtmp2sink.c
Normal file
1064
gst/rtmp2/gstrtmp2sink.c
Normal file
File diff suppressed because it is too large
Load diff
34
gst/rtmp2/gstrtmp2sink.h
Normal file
34
gst/rtmp2/gstrtmp2sink.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2014 David Schleef <ds@schleef.org>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP2_SINK_H_
|
||||
|
||||
#define _GST_RTMP2_SINK_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_RTMP2_SINK (gst_rtmp2_sink_get_type())
|
||||
GType gst_rtmp2_sink_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
839
gst/rtmp2/gstrtmp2src.c
Normal file
839
gst/rtmp2/gstrtmp2src.c
Normal file
|
@ -0,0 +1,839 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2014 David Schleef <ds@schleef.org>
|
||||
* 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 Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
/**
|
||||
* SECTION:element-gstrtmp2src
|
||||
*
|
||||
* The rtmp2src element receives input streams from an RTMP server.
|
||||
*
|
||||
* <refsect2>
|
||||
* <title>Example launch line</title>
|
||||
* |[
|
||||
* gst-launch -v rtmp2src ! decodebin ! fakesink
|
||||
* ]|
|
||||
* FIXME Describe what the pipeline does.
|
||||
* </refsect2>
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstrtmp2src.h"
|
||||
|
||||
#include "gstrtmp2locationhandler.h"
|
||||
#include "rtmp/rtmpclient.h"
|
||||
#include "rtmp/rtmpmessage.h"
|
||||
|
||||
#include <gst/base/gstpushsrc.h>
|
||||
#include <string.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_rtmp2_src_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_rtmp2_src_debug_category
|
||||
|
||||
/* prototypes */
|
||||
#define GST_RTMP2_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTMP2_SRC,GstRtmp2Src))
|
||||
#define GST_IS_RTMP2_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTMP2_SRC))
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstPushSrc parent_instance;
|
||||
|
||||
/* properties */
|
||||
GstRtmpLocation location;
|
||||
gboolean async_connect;
|
||||
|
||||
/* stuff */
|
||||
gboolean running, flushing;
|
||||
GMutex lock;
|
||||
GCond cond;
|
||||
|
||||
GstTask *task;
|
||||
GRecMutex task_lock;
|
||||
|
||||
GMainLoop *loop;
|
||||
GMainContext *context;
|
||||
|
||||
GCancellable *cancellable;
|
||||
GstRtmpConnection *connection;
|
||||
guint32 stream_id;
|
||||
|
||||
GstBuffer *message;
|
||||
gboolean sent_header;
|
||||
GstClockTime last_ts;
|
||||
} GstRtmp2Src;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstPushSrcClass parent_class;
|
||||
} GstRtmp2SrcClass;
|
||||
|
||||
/* GObject virtual functions */
|
||||
static void gst_rtmp2_src_set_property (GObject * object,
|
||||
guint property_id, const GValue * value, GParamSpec * pspec);
|
||||
static void gst_rtmp2_src_get_property (GObject * object,
|
||||
guint property_id, GValue * value, GParamSpec * pspec);
|
||||
static void gst_rtmp2_src_finalize (GObject * object);
|
||||
static void gst_rtmp2_src_uri_handler_init (GstURIHandlerInterface * iface);
|
||||
|
||||
/* GstBaseSrc virtual functions */
|
||||
static gboolean gst_rtmp2_src_start (GstBaseSrc * src);
|
||||
static gboolean gst_rtmp2_src_stop (GstBaseSrc * src);
|
||||
static gboolean gst_rtmp2_src_unlock (GstBaseSrc * src);
|
||||
static gboolean gst_rtmp2_src_unlock_stop (GstBaseSrc * src);
|
||||
static GstFlowReturn gst_rtmp2_src_create (GstBaseSrc * src, guint64 offset,
|
||||
guint size, GstBuffer ** outbuf);
|
||||
|
||||
/* Internal API */
|
||||
static void gst_rtmp2_src_task_func (gpointer user_data);
|
||||
static void client_connect_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
static void start_play_done (GObject * object, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
static void connect_task_done (GObject * object, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_LOCATION,
|
||||
PROP_SCHEME,
|
||||
PROP_HOST,
|
||||
PROP_PORT,
|
||||
PROP_APPLICATION,
|
||||
PROP_STREAM,
|
||||
PROP_SECURE_TOKEN,
|
||||
PROP_USERNAME,
|
||||
PROP_PASSWORD,
|
||||
PROP_AUTHMOD,
|
||||
PROP_TIMEOUT,
|
||||
PROP_TLS_VALIDATION_FLAGS,
|
||||
PROP_ASYNC_CONNECT,
|
||||
};
|
||||
|
||||
/* pad templates */
|
||||
|
||||
static GstStaticPadTemplate gst_rtmp2_src_src_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("video/x-flv")
|
||||
);
|
||||
|
||||
/* class initialization */
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GstRtmp2Src, gst_rtmp2_src, GST_TYPE_PUSH_SRC,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
|
||||
gst_rtmp2_src_uri_handler_init);
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_RTMP_LOCATION_HANDLER, NULL));
|
||||
|
||||
static void
|
||||
gst_rtmp2_src_class_init (GstRtmp2SrcClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstBaseSrcClass *base_src_class = GST_BASE_SRC_CLASS (klass);
|
||||
|
||||
gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
|
||||
&gst_rtmp2_src_src_template);
|
||||
|
||||
gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
|
||||
"RTMP source element", "Source", "Source element for RTMP streams",
|
||||
"Make.TV, Inc. <info@make.tv>");
|
||||
|
||||
gobject_class->set_property = gst_rtmp2_src_set_property;
|
||||
gobject_class->get_property = gst_rtmp2_src_get_property;
|
||||
gobject_class->finalize = gst_rtmp2_src_finalize;
|
||||
base_src_class->start = GST_DEBUG_FUNCPTR (gst_rtmp2_src_start);
|
||||
base_src_class->stop = GST_DEBUG_FUNCPTR (gst_rtmp2_src_stop);
|
||||
base_src_class->unlock = GST_DEBUG_FUNCPTR (gst_rtmp2_src_unlock);
|
||||
base_src_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_rtmp2_src_unlock_stop);
|
||||
base_src_class->create = GST_DEBUG_FUNCPTR (gst_rtmp2_src_create);
|
||||
|
||||
g_object_class_override_property (gobject_class, PROP_LOCATION, "location");
|
||||
g_object_class_override_property (gobject_class, PROP_SCHEME, "scheme");
|
||||
g_object_class_override_property (gobject_class, PROP_HOST, "host");
|
||||
g_object_class_override_property (gobject_class, PROP_PORT, "port");
|
||||
g_object_class_override_property (gobject_class, PROP_APPLICATION,
|
||||
"application");
|
||||
g_object_class_override_property (gobject_class, PROP_STREAM, "stream");
|
||||
g_object_class_override_property (gobject_class, PROP_SECURE_TOKEN,
|
||||
"secure-token");
|
||||
g_object_class_override_property (gobject_class, PROP_USERNAME, "username");
|
||||
g_object_class_override_property (gobject_class, PROP_PASSWORD, "password");
|
||||
g_object_class_override_property (gobject_class, PROP_AUTHMOD, "authmod");
|
||||
g_object_class_override_property (gobject_class, PROP_TIMEOUT, "timeout");
|
||||
g_object_class_override_property (gobject_class, PROP_TLS_VALIDATION_FLAGS,
|
||||
"tls-validation-flags");
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_ASYNC_CONNECT,
|
||||
g_param_spec_boolean ("async-connect", "Async connect",
|
||||
"Connect on READY, otherwise on first push", TRUE,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_rtmp2_src_debug_category, "rtmp2src", 0,
|
||||
"debug category for rtmp2src element");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp2_src_init (GstRtmp2Src * self)
|
||||
{
|
||||
self->async_connect = TRUE;
|
||||
|
||||
g_mutex_init (&self->lock);
|
||||
g_cond_init (&self->cond);
|
||||
|
||||
self->task = gst_task_new (gst_rtmp2_src_task_func, self, NULL);
|
||||
g_rec_mutex_init (&self->task_lock);
|
||||
gst_task_set_lock (self->task, &self->task_lock);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp2_src_uri_handler_init (GstURIHandlerInterface * iface)
|
||||
{
|
||||
gst_rtmp_location_handler_implement_uri_handler (iface, GST_URI_SRC);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp2_src_set_property (GObject * object, guint property_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_LOCATION:
|
||||
gst_rtmp_location_handler_set_uri (GST_RTMP_LOCATION_HANDLER (self),
|
||||
g_value_get_string (value));
|
||||
break;
|
||||
case PROP_SCHEME:
|
||||
GST_OBJECT_LOCK (self);
|
||||
self->location.scheme = g_value_get_enum (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_HOST:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_free (self->location.host);
|
||||
self->location.host = g_value_dup_string (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_PORT:
|
||||
GST_OBJECT_LOCK (self);
|
||||
self->location.port = g_value_get_int (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_APPLICATION:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_free (self->location.application);
|
||||
self->location.application = g_value_dup_string (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_STREAM:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_free (self->location.stream);
|
||||
self->location.stream = g_value_dup_string (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_SECURE_TOKEN:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_free (self->location.secure_token);
|
||||
self->location.secure_token = g_value_dup_string (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_USERNAME:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_free (self->location.username);
|
||||
self->location.username = g_value_dup_string (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_PASSWORD:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_free (self->location.password);
|
||||
self->location.password = g_value_dup_string (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_AUTHMOD:
|
||||
GST_OBJECT_LOCK (self);
|
||||
self->location.authmod = g_value_get_enum (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_TIMEOUT:
|
||||
GST_OBJECT_LOCK (self);
|
||||
self->location.timeout = g_value_get_uint (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_TLS_VALIDATION_FLAGS:
|
||||
GST_OBJECT_LOCK (self);
|
||||
self->location.tls_flags = g_value_get_flags (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_ASYNC_CONNECT:
|
||||
GST_OBJECT_LOCK (self);
|
||||
self->async_connect = g_value_get_boolean (value);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp2_src_get_property (GObject * object, guint property_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_LOCATION:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_take_string (value, gst_rtmp_location_get_string (&self->location,
|
||||
TRUE));
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_SCHEME:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_enum (value, self->location.scheme);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_HOST:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_string (value, self->location.host);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_PORT:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_int (value, self->location.port);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_APPLICATION:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_string (value, self->location.application);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_STREAM:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_string (value, self->location.stream);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_SECURE_TOKEN:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_string (value, self->location.secure_token);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_USERNAME:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_string (value, self->location.username);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_PASSWORD:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_string (value, self->location.password);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_AUTHMOD:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_enum (value, self->location.authmod);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_TIMEOUT:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_uint (value, self->location.timeout);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_TLS_VALIDATION_FLAGS:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_flags (value, self->location.tls_flags);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
case PROP_ASYNC_CONNECT:
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_value_set_boolean (value, self->async_connect);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp2_src_finalize (GObject * object)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (object);
|
||||
|
||||
gst_buffer_replace (&self->message, NULL);
|
||||
|
||||
g_clear_object (&self->cancellable);
|
||||
g_clear_object (&self->connection);
|
||||
|
||||
g_clear_object (&self->task);
|
||||
g_rec_mutex_clear (&self->task_lock);
|
||||
|
||||
g_mutex_clear (&self->lock);
|
||||
g_cond_clear (&self->cond);
|
||||
|
||||
gst_rtmp_location_clear (&self->location);
|
||||
|
||||
G_OBJECT_CLASS (gst_rtmp2_src_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp2_src_start (GstBaseSrc * src)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (src);
|
||||
gboolean async;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
async = self->async_connect;
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
GST_INFO_OBJECT (self, "Starting (%s)", async ? "async" : "delayed");
|
||||
|
||||
g_clear_object (&self->cancellable);
|
||||
|
||||
self->running = TRUE;
|
||||
self->cancellable = g_cancellable_new ();
|
||||
self->stream_id = 0;
|
||||
self->sent_header = FALSE;
|
||||
self->last_ts = GST_CLOCK_TIME_NONE;
|
||||
|
||||
if (async) {
|
||||
gst_task_start (self->task);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
quit_invoker (gpointer user_data)
|
||||
{
|
||||
g_main_loop_quit (user_data);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
stop_task (GstRtmp2Src * self)
|
||||
{
|
||||
gst_task_stop (self->task);
|
||||
self->running = FALSE;
|
||||
|
||||
if (self->cancellable) {
|
||||
GST_DEBUG_OBJECT (self, "Cancelling");
|
||||
g_cancellable_cancel (self->cancellable);
|
||||
}
|
||||
|
||||
if (self->loop) {
|
||||
GST_DEBUG_OBJECT (self, "Stopping loop");
|
||||
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT_IDLE,
|
||||
quit_invoker, g_main_loop_ref (self->loop),
|
||||
(GDestroyNotify) g_main_loop_unref);
|
||||
}
|
||||
|
||||
g_cond_broadcast (&self->cond);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp2_src_stop (GstBaseSrc * src)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (src);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "stop");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
stop_task (self);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
gst_task_join (self->task);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp2_src_unlock (GstBaseSrc * src)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (src);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "unlock");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
self->flushing = TRUE;
|
||||
g_cond_broadcast (&self->cond);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp2_src_unlock_stop (GstBaseSrc * src)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (src);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "unlock_stop");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
self->flushing = FALSE;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_rtmp2_src_create (GstBaseSrc * src, guint64 offset, guint size,
|
||||
GstBuffer ** outbuf)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (src);
|
||||
GstBuffer *message, *buffer;
|
||||
GstRtmpMeta *meta;
|
||||
guint32 timestamp = 0;
|
||||
|
||||
static const guint8 flv_header_data[] = {
|
||||
0x46, 0x4c, 0x56, 0x01, 0x01, 0x00, 0x00, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
GST_LOG_OBJECT (self, "create");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
|
||||
if (self->running) {
|
||||
gst_task_start (self->task);
|
||||
}
|
||||
|
||||
while (!self->message) {
|
||||
if (!self->running) {
|
||||
g_mutex_unlock (&self->lock);
|
||||
return GST_FLOW_EOS;
|
||||
}
|
||||
if (self->flushing) {
|
||||
g_mutex_unlock (&self->lock);
|
||||
return GST_FLOW_FLUSHING;
|
||||
}
|
||||
g_cond_wait (&self->cond, &self->lock);
|
||||
}
|
||||
|
||||
message = self->message;
|
||||
self->message = NULL;
|
||||
g_cond_signal (&self->cond);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
meta = gst_buffer_get_rtmp_meta (message);
|
||||
if (!meta) {
|
||||
GST_ELEMENT_ERROR (self, CORE, FAILED,
|
||||
("Internal error: No RTMP meta on buffer"),
|
||||
("No RTMP meta on %" GST_PTR_FORMAT, message));
|
||||
gst_buffer_unref (message);
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
if (GST_BUFFER_DTS_IS_VALID (message)) {
|
||||
GstClockTime last_ts = self->last_ts, ts = GST_BUFFER_DTS (message);
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (last_ts) && last_ts > ts) {
|
||||
GST_LOG_OBJECT (self, "Timestamp regression: %" GST_TIME_FORMAT
|
||||
" > %" GST_TIME_FORMAT, GST_TIME_ARGS (last_ts), GST_TIME_ARGS (ts));
|
||||
}
|
||||
|
||||
self->last_ts = ts;
|
||||
timestamp = ts / GST_MSECOND;
|
||||
}
|
||||
|
||||
buffer = gst_buffer_copy_region (message, GST_BUFFER_COPY_MEMORY, 0, -1);
|
||||
|
||||
{
|
||||
guint8 *tag_header = g_malloc (11);
|
||||
GstMemory *memory =
|
||||
gst_memory_new_wrapped (0, tag_header, 11, 0, 11, tag_header, g_free);
|
||||
GST_WRITE_UINT8 (tag_header, meta->type);
|
||||
GST_WRITE_UINT24_BE (tag_header + 1, meta->size);
|
||||
GST_WRITE_UINT24_BE (tag_header + 4, timestamp);
|
||||
GST_WRITE_UINT8 (tag_header + 7, timestamp >> 24);
|
||||
GST_WRITE_UINT24_BE (tag_header + 8, 0);
|
||||
gst_buffer_prepend_memory (buffer, memory);
|
||||
}
|
||||
|
||||
{
|
||||
guint8 *tag_footer = g_malloc (4);
|
||||
GstMemory *memory =
|
||||
gst_memory_new_wrapped (0, tag_footer, 4, 0, 4, tag_footer, g_free);
|
||||
GST_WRITE_UINT32_BE (tag_footer, meta->size + 11);
|
||||
gst_buffer_append_memory (buffer, memory);
|
||||
}
|
||||
|
||||
if (!self->sent_header) {
|
||||
GstMemory *memory = gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
|
||||
(guint8 *) flv_header_data, sizeof flv_header_data, 0,
|
||||
sizeof flv_header_data, NULL, NULL);
|
||||
gst_buffer_prepend_memory (buffer, memory);
|
||||
self->sent_header = TRUE;
|
||||
}
|
||||
|
||||
*outbuf = buffer;
|
||||
|
||||
gst_buffer_unref (message);
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
/* Mainloop task */
|
||||
static void
|
||||
gst_rtmp2_src_task_func (gpointer user_data)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (user_data);
|
||||
GMainContext *context;
|
||||
GMainLoop *loop;
|
||||
GTask *connector;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "gst_rtmp2_src_task starting");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
context = self->context = g_main_context_new ();
|
||||
g_main_context_push_thread_default (context);
|
||||
loop = self->loop = g_main_loop_new (context, TRUE);
|
||||
connector = g_task_new (self, self->cancellable, connect_task_done, NULL);
|
||||
GST_OBJECT_LOCK (self);
|
||||
gst_rtmp_client_connect_async (&self->location, self->cancellable,
|
||||
client_connect_done, connector);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
g_clear_pointer (&self->loop, g_main_loop_unref);
|
||||
g_clear_pointer (&self->connection, gst_rtmp_connection_close_and_unref);
|
||||
g_cond_broadcast (&self->cond);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
while (g_main_context_pending (context)) {
|
||||
GST_DEBUG_OBJECT (self, "iterating main context to clean up");
|
||||
g_main_context_iteration (context, FALSE);
|
||||
}
|
||||
|
||||
g_main_context_pop_thread_default (context);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
g_clear_pointer (&self->context, g_main_context_unref);
|
||||
gst_buffer_replace (&self->message, NULL);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "gst_rtmp2_src_task exiting");
|
||||
}
|
||||
|
||||
static void
|
||||
client_connect_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task = user_data;
|
||||
GstRtmp2Src *self = g_task_get_source_object (task);
|
||||
GError *error = NULL;
|
||||
GstRtmpConnection *connection;
|
||||
|
||||
connection = gst_rtmp_client_connect_finish (result, &error);
|
||||
if (!connection) {
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_task_set_task_data (task, connection, g_object_unref);
|
||||
|
||||
if (g_task_return_error_if_cancelled (task)) {
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
gst_rtmp_client_start_play_async (connection, self->location.stream,
|
||||
g_task_get_cancellable (task), start_play_done, task);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
static void
|
||||
start_play_done (GObject * source, GAsyncResult * result, gpointer user_data)
|
||||
{
|
||||
GTask *task = G_TASK (user_data);
|
||||
GstRtmp2Src *self = g_task_get_source_object (task);
|
||||
GstRtmpConnection *connection = g_task_get_task_data (task);
|
||||
GError *error = NULL;
|
||||
|
||||
if (g_task_return_error_if_cancelled (task)) {
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gst_rtmp_client_start_play_finish (connection, result,
|
||||
&self->stream_id, &error)) {
|
||||
g_task_return_pointer (task, g_object_ref (connection),
|
||||
gst_rtmp_connection_close_and_unref);
|
||||
} else {
|
||||
g_task_return_error (task, error);
|
||||
}
|
||||
|
||||
g_task_set_task_data (task, NULL, NULL);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
got_message (GstRtmpConnection * connection, GstBuffer * buffer,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (user_data);
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
guint32 min_size = 1;
|
||||
|
||||
g_return_if_fail (meta);
|
||||
|
||||
if (meta->mstream != self->stream_id) {
|
||||
GST_DEBUG_OBJECT (self, "Ignoring %s message with stream %" G_GUINT32_FORMAT
|
||||
" != %" G_GUINT32_FORMAT, gst_rtmp_message_type_get_nick (meta->type),
|
||||
meta->mstream, self->stream_id);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (meta->type) {
|
||||
case GST_RTMP_MESSAGE_TYPE_VIDEO:
|
||||
min_size = 6;
|
||||
break;
|
||||
|
||||
case GST_RTMP_MESSAGE_TYPE_AUDIO:
|
||||
min_size = 2;
|
||||
break;
|
||||
|
||||
case GST_RTMP_MESSAGE_TYPE_DATA_AMF0:
|
||||
break;
|
||||
|
||||
default:
|
||||
GST_DEBUG_OBJECT (self, "Ignoring %s message, wrong type",
|
||||
gst_rtmp_message_type_get_nick (meta->type));
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta->size < min_size) {
|
||||
GST_DEBUG_OBJECT (self, "Ignoring too small %s message (%" G_GUINT32_FORMAT
|
||||
" < %" G_GUINT32_FORMAT ")",
|
||||
gst_rtmp_message_type_get_nick (meta->type), meta->size, min_size);
|
||||
return;
|
||||
}
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
while (self->message) {
|
||||
if (!self->running) {
|
||||
goto out;
|
||||
}
|
||||
g_cond_wait (&self->cond, &self->lock);
|
||||
}
|
||||
|
||||
self->message = gst_buffer_ref (buffer);
|
||||
g_cond_signal (&self->cond);
|
||||
|
||||
out:
|
||||
g_mutex_unlock (&self->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
error_callback (GstRtmpConnection * connection, GstRtmp2Src * self)
|
||||
{
|
||||
g_mutex_lock (&self->lock);
|
||||
if (self->cancellable) {
|
||||
g_cancellable_cancel (self->cancellable);
|
||||
} else if (self->loop) {
|
||||
GST_INFO_OBJECT (self, "Connection error");
|
||||
stop_task (self);
|
||||
}
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
control_callback (GstRtmpConnection * connection, gint uc_type,
|
||||
guint stream_id, GstRtmp2Src * self)
|
||||
{
|
||||
GST_INFO_OBJECT (self, "stream %u got %s", stream_id,
|
||||
gst_rtmp_user_control_type_get_nick (uc_type));
|
||||
|
||||
if (uc_type == GST_RTMP_USER_CONTROL_TYPE_STREAM_EOF && stream_id == 1) {
|
||||
GST_INFO_OBJECT (self, "went EOS");
|
||||
stop_task (self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
send_connect_error (GstRtmp2Src * self, GError * error)
|
||||
{
|
||||
if (!error) {
|
||||
GST_ERROR_OBJECT (self, "Connect failed with NULL error");
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("Failed to connect"), (NULL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
GST_DEBUG_OBJECT (self, "Connection was cancelled (%s)",
|
||||
GST_STR_NULL (error->message));
|
||||
return;
|
||||
}
|
||||
|
||||
GST_ERROR_OBJECT (self, "Failed to connect (%s:%d): %s",
|
||||
g_quark_to_string (error->domain), error->code,
|
||||
GST_STR_NULL (error->message));
|
||||
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_AUTHORIZED,
|
||||
("Not authorized to connect"), ("%s", GST_STR_NULL (error->message)));
|
||||
} else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("Could not connect"), ("%s", GST_STR_NULL (error->message)));
|
||||
} else {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
||||
("Failed to connect"),
|
||||
("error %s:%d: %s", g_quark_to_string (error->domain), error->code,
|
||||
GST_STR_NULL (error->message)));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
connect_task_done (GObject * object, GAsyncResult * result, gpointer user_data)
|
||||
{
|
||||
GstRtmp2Src *self = GST_RTMP2_SRC (object);
|
||||
GTask *task = G_TASK (result);
|
||||
GError *error = NULL;
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
|
||||
g_warn_if_fail (g_task_is_valid (task, object));
|
||||
|
||||
if (self->cancellable == g_task_get_cancellable (task)) {
|
||||
g_clear_object (&self->cancellable);
|
||||
}
|
||||
|
||||
self->connection = g_task_propagate_pointer (task, &error);
|
||||
if (self->connection) {
|
||||
gst_rtmp_connection_set_input_handler (self->connection,
|
||||
got_message, g_object_ref (self), g_object_unref);
|
||||
g_signal_connect_object (self->connection, "error",
|
||||
G_CALLBACK (error_callback), self, 0);
|
||||
g_signal_connect_object (self->connection, "stream-control",
|
||||
G_CALLBACK (control_callback), self, 0);
|
||||
} else {
|
||||
send_connect_error (self, error);
|
||||
stop_task (self);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_cond_broadcast (&self->cond);
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
34
gst/rtmp2/gstrtmp2src.h
Normal file
34
gst/rtmp2/gstrtmp2src.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2014 David Schleef <ds@schleef.org>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP2_SRC_H_
|
||||
|
||||
#define _GST_RTMP2_SRC_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_RTMP2_SRC (gst_rtmp2_src_get_type())
|
||||
GType gst_rtmp2_src_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
24
gst/rtmp2/meson.build
Normal file
24
gst/rtmp2/meson.build
Normal file
|
@ -0,0 +1,24 @@
|
|||
rtmp2_sources = [
|
||||
'gstrtmp2.c',
|
||||
'gstrtmp2locationhandler.c',
|
||||
'gstrtmp2sink.c',
|
||||
'gstrtmp2src.c',
|
||||
'rtmp/amf.c',
|
||||
'rtmp/rtmpchunkstream.c',
|
||||
'rtmp/rtmpclient.c',
|
||||
'rtmp/rtmpconnection.c',
|
||||
'rtmp/rtmphandshake.c',
|
||||
'rtmp/rtmpmessage.c',
|
||||
'rtmp/rtmputils.c',
|
||||
]
|
||||
|
||||
gstrtmp2 = library('gstrtmp2',
|
||||
rtmp2_sources,
|
||||
c_args : gst_plugins_bad_args,
|
||||
include_directories : [configinc, libsinc],
|
||||
dependencies : [gstbase_dep, gio_dep, libm],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
pkgconfig.generate(gstrtmp2, install_dir : plugins_pkgconfig_install_dir)
|
||||
plugins += [gstrtmp2]
|
1141
gst/rtmp2/rtmp/amf.c
Normal file
1141
gst/rtmp2/rtmp/amf.c
Normal file
File diff suppressed because it is too large
Load diff
112
gst/rtmp2/rtmp/amf.h
Normal file
112
gst/rtmp2/rtmp/amf.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/* GStreamer RTMP Library
|
||||
* Copyright (C) 2014 David Schleef <ds@schleef.org>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP_AMF_H_
|
||||
#define _GST_RTMP_AMF_H_
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_AMF_TYPE_INVALID = -1,
|
||||
GST_AMF_TYPE_NUMBER = 0,
|
||||
GST_AMF_TYPE_BOOLEAN = 1,
|
||||
GST_AMF_TYPE_STRING = 2,
|
||||
GST_AMF_TYPE_OBJECT = 3,
|
||||
GST_AMF_TYPE_MOVIECLIP = 4,
|
||||
GST_AMF_TYPE_NULL = 5,
|
||||
GST_AMF_TYPE_UNDEFINED = 6,
|
||||
GST_AMF_TYPE_REFERENCE = 7,
|
||||
GST_AMF_TYPE_ECMA_ARRAY = 8,
|
||||
GST_AMF_TYPE_OBJECT_END = 9,
|
||||
GST_AMF_TYPE_STRICT_ARRAY = 10,
|
||||
GST_AMF_TYPE_DATE = 11,
|
||||
GST_AMF_TYPE_LONG_STRING = 12,
|
||||
GST_AMF_TYPE_UNSUPPORTED = 13,
|
||||
GST_AMF_TYPE_RECORDSET = 14,
|
||||
GST_AMF_TYPE_XML_DOCUMENT = 15,
|
||||
GST_AMF_TYPE_TYPED_OBJECT = 16,
|
||||
GST_AMF_TYPE_AVMPLUS_OBJECT = 17
|
||||
} GstAmfType;
|
||||
|
||||
const gchar * gst_amf_type_get_nick (GstAmfType type);
|
||||
|
||||
typedef struct _GstAmfNode GstAmfNode;
|
||||
|
||||
GstAmfNode * gst_amf_node_new_null (void);
|
||||
GstAmfNode * gst_amf_node_new_number (gdouble value);
|
||||
GstAmfNode * gst_amf_node_new_boolean (gboolean value);
|
||||
GstAmfNode * gst_amf_node_new_string (const gchar * value, gssize size);
|
||||
GstAmfNode * gst_amf_node_new_take_string (gchar * value, gssize size);
|
||||
GstAmfNode * gst_amf_node_new_object (void);
|
||||
|
||||
GstAmfNode * gst_amf_node_copy (const GstAmfNode * node);
|
||||
void gst_amf_node_free (gpointer ptr);
|
||||
|
||||
GstAmfType gst_amf_node_get_type (const GstAmfNode * node);
|
||||
gdouble gst_amf_node_get_number (const GstAmfNode * node);
|
||||
gboolean gst_amf_node_get_boolean (const GstAmfNode * node);
|
||||
gchar * gst_amf_node_get_string (const GstAmfNode * node, gsize * size);
|
||||
const gchar * gst_amf_node_peek_string (const GstAmfNode * node, gsize * size);
|
||||
|
||||
const GstAmfNode * gst_amf_node_get_field (const GstAmfNode * node,
|
||||
const gchar * name);
|
||||
const GstAmfNode * gst_amf_node_get_field_by_index (const GstAmfNode * node,
|
||||
guint index);
|
||||
guint gst_amf_node_get_num_fields (const GstAmfNode * node);
|
||||
|
||||
const GstAmfNode * gst_amf_node_get_element (const GstAmfNode * node,
|
||||
guint index);
|
||||
guint gst_amf_node_get_num_elements (const GstAmfNode * node);
|
||||
|
||||
void gst_amf_node_set_number (GstAmfNode * node, gdouble value);
|
||||
void gst_amf_node_set_boolean (GstAmfNode * node, gboolean value);
|
||||
void gst_amf_node_set_string (GstAmfNode * node, const gchar * value, gssize size);
|
||||
void gst_amf_node_take_string (GstAmfNode * node, gchar * value, gssize size);
|
||||
|
||||
void gst_amf_node_append_field (GstAmfNode * node,
|
||||
const gchar * name, const GstAmfNode * value);
|
||||
void gst_amf_node_append_take_field (GstAmfNode * node,
|
||||
const gchar * name, GstAmfNode * value);
|
||||
void gst_amf_node_append_field_number (GstAmfNode * node,
|
||||
const gchar * name, gdouble value);
|
||||
void gst_amf_node_append_field_boolean (GstAmfNode * node,
|
||||
const gchar * name, gboolean value);
|
||||
void gst_amf_node_append_field_string (GstAmfNode * node,
|
||||
const gchar * name, const gchar * value, gssize size);
|
||||
void gst_amf_node_append_field_take_string (GstAmfNode * node,
|
||||
const gchar * name, gchar * value, gssize size);
|
||||
|
||||
void gst_amf_node_dump (const GstAmfNode * node, gint indent,
|
||||
GString * string);
|
||||
|
||||
GPtrArray * gst_amf_parse_command (const guint8 * data, gsize size,
|
||||
gdouble * transaction_id, gchar ** command_name);
|
||||
|
||||
GBytes * gst_amf_serialize_command (gdouble transaction_id,
|
||||
const gchar * command_name, const GstAmfNode * argument, ...) G_GNUC_NULL_TERMINATED;
|
||||
GBytes * gst_amf_serialize_command_valist (gdouble transaction_id,
|
||||
const gchar * command_name, const GstAmfNode * argument, va_list va_args);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
714
gst/rtmp2/rtmp/rtmpchunkstream.c
Normal file
714
gst/rtmp2/rtmp/rtmpchunkstream.c
Normal file
|
@ -0,0 +1,714 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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 Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "rtmpchunkstream.h"
|
||||
#include "rtmputils.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_rtmp_chunk_stream_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_rtmp_chunk_stream_debug_category
|
||||
|
||||
static void
|
||||
init_debug (void)
|
||||
{
|
||||
static volatile gsize done = 0;
|
||||
if (g_once_init_enter (&done)) {
|
||||
GST_DEBUG_CATEGORY_INIT (gst_rtmp_chunk_stream_debug_category,
|
||||
"rtmpchunkstream", 0, "debug category for rtmp chunk streams");
|
||||
g_once_init_leave (&done, 1);
|
||||
}
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
CHUNK_BYTE_TWOBYTE = 0,
|
||||
CHUNK_BYTE_THREEBYTE = 1,
|
||||
CHUNK_BYTE_MASK = 0x3f,
|
||||
CHUNK_STREAM_MIN_TWOBYTE = 0x40,
|
||||
CHUNK_STREAM_MIN_THREEBYTE = 0x140,
|
||||
CHUNK_STREAM_MAX_THREEBYTE = 0x1003f,
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CHUNK_TYPE_0 = 0,
|
||||
CHUNK_TYPE_1 = 1,
|
||||
CHUNK_TYPE_2 = 2,
|
||||
CHUNK_TYPE_3 = 3,
|
||||
} ChunkType;
|
||||
|
||||
static const gsize chunk_header_sizes[4] = { 11, 7, 3, 0 };
|
||||
|
||||
struct _GstRtmpChunkStream
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
GstRtmpMeta *meta;
|
||||
GstMapInfo map; /* Only used for parsing */
|
||||
guint32 id;
|
||||
guint32 offset;
|
||||
guint64 bytes;
|
||||
};
|
||||
|
||||
struct _GstRtmpChunkStreams
|
||||
{
|
||||
GArray *array;
|
||||
};
|
||||
|
||||
static inline gboolean
|
||||
chunk_stream_is_open (GstRtmpChunkStream * cstream)
|
||||
{
|
||||
return cstream->map.data != NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
chunk_stream_take_buffer (GstRtmpChunkStream * cstream, GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
g_assert (meta);
|
||||
g_assert (cstream->buffer == NULL);
|
||||
cstream->buffer = buffer;
|
||||
cstream->meta = meta;
|
||||
}
|
||||
|
||||
static void
|
||||
chunk_stream_clear (GstRtmpChunkStream * cstream)
|
||||
{
|
||||
if (chunk_stream_is_open (cstream)) {
|
||||
gst_buffer_unmap (cstream->buffer, &cstream->map);
|
||||
cstream->map.data = NULL;
|
||||
}
|
||||
|
||||
gst_buffer_replace (&cstream->buffer, NULL);
|
||||
cstream->meta = NULL;
|
||||
cstream->offset = 0;
|
||||
}
|
||||
|
||||
static guint32
|
||||
chunk_stream_next_size (GstRtmpChunkStream * cstream, guint32 chunk_size)
|
||||
{
|
||||
guint32 size, offset;
|
||||
|
||||
size = cstream->meta->size;
|
||||
offset = cstream->offset;
|
||||
|
||||
g_return_val_if_fail (offset <= size, 0);
|
||||
return MIN (size - offset, chunk_size);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
needs_ext_ts (GstRtmpMeta * meta)
|
||||
{
|
||||
return meta->ts_delta >= 0xffffff;
|
||||
}
|
||||
|
||||
|
||||
static guint32
|
||||
dts_to_abs_ts (GstBuffer * buffer)
|
||||
{
|
||||
GstClockTime dts = GST_BUFFER_DTS (buffer);
|
||||
guint32 ret = 0;
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (dts)) {
|
||||
ret = gst_util_uint64_scale_round (dts, 1, GST_MSECOND);
|
||||
}
|
||||
|
||||
GST_TRACE ("Converted DTS %" GST_TIME_FORMAT " into abs TS %"
|
||||
G_GUINT32_FORMAT " ms", GST_TIME_ARGS (dts), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dts_diff_to_delta_ts (GstBuffer * old_buffer, GstBuffer * buffer,
|
||||
guint32 * out_ts)
|
||||
{
|
||||
GstClockTime dts = GST_BUFFER_DTS (buffer),
|
||||
old_dts = GST_BUFFER_DTS (old_buffer);
|
||||
guint32 abs_ts, old_abs_ts, delta_32 = 0;
|
||||
|
||||
if (!GST_CLOCK_TIME_IS_VALID (dts) || !GST_CLOCK_TIME_IS_VALID (old_dts)) {
|
||||
GST_LOG ("Timestamps not valid; using delta TS 0");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ABS (GST_CLOCK_DIFF (old_dts, dts)) > GST_MSECOND * G_MAXINT32) {
|
||||
GST_WARNING ("Timestamp delta too large: %" GST_TIME_FORMAT " -> %"
|
||||
GST_TIME_FORMAT, GST_TIME_ARGS (old_dts), GST_TIME_ARGS (dts));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
abs_ts = gst_util_uint64_scale_round (dts, 1, GST_MSECOND);
|
||||
old_abs_ts = gst_util_uint64_scale_round (old_dts, 1, GST_MSECOND);
|
||||
|
||||
/* underflow wraps around */
|
||||
delta_32 = abs_ts - old_abs_ts;
|
||||
|
||||
GST_TRACE ("Converted DTS %" GST_TIME_FORMAT " (%" G_GUINT32_FORMAT
|
||||
" ms) -> %" GST_TIME_FORMAT " (%" G_GUINT32_FORMAT " ms) into delta TS %"
|
||||
G_GUINT32_FORMAT " ms", GST_TIME_ARGS (old_dts), old_abs_ts,
|
||||
GST_TIME_ARGS (dts), abs_ts, delta_32);
|
||||
|
||||
out:
|
||||
*out_ts = delta_32;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static ChunkType
|
||||
select_chunk_type (GstRtmpChunkStream * cstream, GstBuffer * buffer)
|
||||
{
|
||||
GstBuffer *old_buffer = cstream->buffer;
|
||||
GstRtmpMeta *meta, *old_meta;
|
||||
|
||||
g_return_val_if_fail (buffer, -1);
|
||||
|
||||
meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
|
||||
g_return_val_if_fail (meta, -1);
|
||||
g_return_val_if_fail (gst_rtmp_message_type_is_valid (meta->type), -1);
|
||||
|
||||
meta->size = gst_buffer_get_size (buffer);
|
||||
meta->cstream = cstream->id;
|
||||
|
||||
if (!old_buffer) {
|
||||
GST_TRACE ("Picking header 0: no previous header");
|
||||
meta->ts_delta = dts_to_abs_ts (buffer);
|
||||
return CHUNK_TYPE_0;
|
||||
}
|
||||
|
||||
old_meta = gst_buffer_get_rtmp_meta (old_buffer);
|
||||
|
||||
if (old_meta->mstream != meta->mstream) {
|
||||
GST_TRACE ("Picking header 0: stream mismatch; "
|
||||
"want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT,
|
||||
old_meta->mstream, meta->mstream);
|
||||
meta->ts_delta = dts_to_abs_ts (buffer);
|
||||
return CHUNK_TYPE_0;
|
||||
}
|
||||
|
||||
if (!dts_diff_to_delta_ts (old_buffer, buffer, &meta->ts_delta)) {
|
||||
GST_TRACE ("Picking header 0: timestamp delta overflow");
|
||||
meta->ts_delta = dts_to_abs_ts (buffer);
|
||||
return CHUNK_TYPE_0;
|
||||
}
|
||||
|
||||
/* now at least type 1 */
|
||||
|
||||
if (old_meta->type != meta->type) {
|
||||
GST_TRACE ("Picking header 1: type mismatch; want %d got %d",
|
||||
old_meta->type, meta->type);
|
||||
return CHUNK_TYPE_1;
|
||||
}
|
||||
|
||||
if (old_meta->size != meta->size) {
|
||||
GST_TRACE ("Picking header 1: size mismatch; "
|
||||
"want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT,
|
||||
old_meta->size, meta->size);
|
||||
return CHUNK_TYPE_1;
|
||||
}
|
||||
|
||||
/* now at least type 2 */
|
||||
|
||||
if (old_meta->ts_delta != meta->ts_delta) {
|
||||
GST_TRACE ("Picking header 2: timestamp delta mismatch; "
|
||||
"want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT,
|
||||
old_meta->ts_delta, meta->ts_delta);
|
||||
return CHUNK_TYPE_2;
|
||||
}
|
||||
|
||||
/* now at least type 3 */
|
||||
|
||||
GST_TRACE ("Picking header 3");
|
||||
return CHUNK_TYPE_3;
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
serialize_next (GstRtmpChunkStream * cstream, guint32 chunk_size,
|
||||
ChunkType type)
|
||||
{
|
||||
GstRtmpMeta *meta = cstream->meta;
|
||||
guint8 small_stream_id;
|
||||
gsize header_size = chunk_header_sizes[type], offset;
|
||||
gboolean ext_ts;
|
||||
GstBuffer *ret;
|
||||
GstMapInfo map;
|
||||
|
||||
GST_TRACE ("Serializing a chunk of type %d, offset %" G_GUINT32_FORMAT,
|
||||
type, cstream->offset);
|
||||
|
||||
if (cstream->id < CHUNK_STREAM_MIN_TWOBYTE) {
|
||||
small_stream_id = cstream->id;
|
||||
header_size += 1;
|
||||
} else if (cstream->id < CHUNK_STREAM_MIN_THREEBYTE) {
|
||||
small_stream_id = CHUNK_BYTE_TWOBYTE;
|
||||
header_size += 2;
|
||||
} else {
|
||||
small_stream_id = CHUNK_BYTE_THREEBYTE;
|
||||
header_size += 3;
|
||||
}
|
||||
|
||||
ext_ts = needs_ext_ts (meta);
|
||||
if (ext_ts) {
|
||||
header_size += 4;
|
||||
}
|
||||
|
||||
GST_TRACE ("Allocating buffer, header size %" G_GSIZE_FORMAT, header_size);
|
||||
|
||||
ret = gst_buffer_new_allocate (NULL, header_size, NULL);
|
||||
if (!ret) {
|
||||
GST_ERROR ("Failed to allocate chunk buffer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!gst_buffer_map (ret, &map, GST_MAP_WRITE)) {
|
||||
GST_ERROR ("Failed to map %" GST_PTR_FORMAT, ret);
|
||||
gst_buffer_unref (ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Chunk Basic Header */
|
||||
GST_WRITE_UINT8 (map.data, (type << 6) | small_stream_id);
|
||||
offset = 1;
|
||||
|
||||
switch (small_stream_id) {
|
||||
case CHUNK_BYTE_TWOBYTE:
|
||||
GST_WRITE_UINT8 (map.data + 1, cstream->id - CHUNK_STREAM_MIN_TWOBYTE);
|
||||
offset += 1;
|
||||
break;
|
||||
|
||||
case CHUNK_BYTE_THREEBYTE:
|
||||
GST_WRITE_UINT16_LE (map.data + 1,
|
||||
cstream->id - CHUNK_STREAM_MIN_TWOBYTE);
|
||||
offset += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case CHUNK_TYPE_0:
|
||||
/* SRSLY: "Message stream ID is stored in little-endian format." */
|
||||
GST_WRITE_UINT32_LE (map.data + offset + 7, meta->mstream);
|
||||
/* no break */
|
||||
case CHUNK_TYPE_1:
|
||||
GST_WRITE_UINT24_BE (map.data + offset + 3, meta->size);
|
||||
GST_WRITE_UINT8 (map.data + offset + 6, meta->type);
|
||||
/* no break */
|
||||
case CHUNK_TYPE_2:
|
||||
GST_WRITE_UINT24_BE (map.data + offset,
|
||||
ext_ts ? 0xffffff : meta->ts_delta);
|
||||
/* no break */
|
||||
case CHUNK_TYPE_3:
|
||||
offset += chunk_header_sizes[type];
|
||||
|
||||
if (ext_ts) {
|
||||
GST_WRITE_UINT32_BE (map.data + offset, meta->ts_delta);
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
g_assert (offset == header_size);
|
||||
GST_MEMDUMP (">>> chunk header", map.data, offset);
|
||||
|
||||
gst_buffer_unmap (ret, &map);
|
||||
|
||||
GST_BUFFER_OFFSET (ret) = GST_BUFFER_OFFSET_IS_VALID (cstream->buffer) ?
|
||||
GST_BUFFER_OFFSET (cstream->buffer) + cstream->offset : cstream->bytes;
|
||||
GST_BUFFER_OFFSET_END (ret) = GST_BUFFER_OFFSET (ret);
|
||||
|
||||
if (meta->size > 0) {
|
||||
guint32 payload_size = chunk_stream_next_size (cstream, chunk_size);
|
||||
|
||||
GST_TRACE ("Appending %" G_GUINT32_FORMAT " bytes of payload",
|
||||
payload_size);
|
||||
|
||||
gst_buffer_copy_into (ret, cstream->buffer, GST_BUFFER_COPY_MEMORY,
|
||||
cstream->offset, payload_size);
|
||||
|
||||
GST_BUFFER_OFFSET_END (ret) += payload_size;
|
||||
cstream->offset += payload_size;
|
||||
cstream->bytes += payload_size;
|
||||
} else {
|
||||
GST_TRACE ("Chunk has no payload");
|
||||
}
|
||||
|
||||
gst_rtmp_buffer_dump (ret, ">>> chunk");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_chunk_stream_clear (GstRtmpChunkStream * cstream)
|
||||
{
|
||||
g_return_if_fail (cstream);
|
||||
GST_LOG ("Clearing chunk stream %" G_GUINT32_FORMAT, cstream->id);
|
||||
chunk_stream_clear (cstream);
|
||||
}
|
||||
|
||||
guint32
|
||||
gst_rtmp_chunk_stream_parse_id (const guint8 * data, gsize size)
|
||||
{
|
||||
guint32 ret;
|
||||
|
||||
if (size < 1) {
|
||||
GST_TRACE ("Not enough bytes to read ID");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = GST_READ_UINT8 (data) & CHUNK_BYTE_MASK;
|
||||
|
||||
switch (ret) {
|
||||
case CHUNK_BYTE_TWOBYTE:
|
||||
if (size < 2) {
|
||||
GST_TRACE ("Not enough bytes to read two-byte ID");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = GST_READ_UINT8 (data + 1) + CHUNK_STREAM_MIN_TWOBYTE;
|
||||
break;
|
||||
|
||||
case CHUNK_BYTE_THREEBYTE:
|
||||
if (size < 3) {
|
||||
GST_TRACE ("Not enough bytes to read three-byte ID");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = GST_READ_UINT16_LE (data + 1) + CHUNK_STREAM_MIN_TWOBYTE;
|
||||
break;
|
||||
}
|
||||
|
||||
GST_TRACE ("Parsed chunk stream ID %" G_GUINT32_FORMAT, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
guint32
|
||||
gst_rtmp_chunk_stream_parse_header (GstRtmpChunkStream * cstream,
|
||||
const guint8 * data, gsize size)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
GstRtmpMeta *meta;
|
||||
const guint8 *message_header;
|
||||
guint32 header_size;
|
||||
ChunkType type;
|
||||
gboolean has_abs_timestamp = FALSE;
|
||||
|
||||
g_return_val_if_fail (cstream, 0);
|
||||
g_return_val_if_fail (cstream->id == gst_rtmp_chunk_stream_parse_id (data,
|
||||
size), 0);
|
||||
|
||||
type = GST_READ_UINT8 (data) >> 6;
|
||||
GST_TRACE ("Parsing chunk stream %" G_GUINT32_FORMAT " header type %d",
|
||||
cstream->id, type);
|
||||
|
||||
switch (GST_READ_UINT8 (data) & CHUNK_BYTE_MASK) {
|
||||
case CHUNK_BYTE_TWOBYTE:
|
||||
header_size = 2;
|
||||
break;
|
||||
case CHUNK_BYTE_THREEBYTE:
|
||||
header_size = 3;
|
||||
break;
|
||||
default:
|
||||
header_size = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
message_header = data + header_size;
|
||||
header_size += chunk_header_sizes[type];
|
||||
|
||||
if (cstream->buffer) {
|
||||
buffer = cstream->buffer;
|
||||
meta = cstream->meta;
|
||||
g_assert (meta->cstream == cstream->id);
|
||||
} else {
|
||||
buffer = gst_buffer_new ();
|
||||
GST_BUFFER_DTS (buffer) = 0;
|
||||
GST_BUFFER_OFFSET (buffer) = cstream->bytes;
|
||||
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
|
||||
|
||||
meta = gst_buffer_add_rtmp_meta (buffer);
|
||||
meta->cstream = cstream->id;
|
||||
|
||||
chunk_stream_take_buffer (cstream, buffer);
|
||||
GST_DEBUG ("Starting parse with new %" GST_PTR_FORMAT, buffer);
|
||||
}
|
||||
|
||||
if (size < header_size) {
|
||||
GST_TRACE ("not enough bytes to read header");
|
||||
return header_size;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case CHUNK_TYPE_0:
|
||||
has_abs_timestamp = TRUE;
|
||||
/* SRSLY: "Message stream ID is stored in little-endian format." */
|
||||
meta->mstream = GST_READ_UINT32_LE (message_header + 7);
|
||||
/* no break */
|
||||
case CHUNK_TYPE_1:
|
||||
meta->type = GST_READ_UINT8 (message_header + 6);
|
||||
meta->size = GST_READ_UINT24_BE (message_header + 3);
|
||||
/* no break */
|
||||
case CHUNK_TYPE_2:
|
||||
meta->ts_delta = GST_READ_UINT24_BE (message_header);
|
||||
/* no break */
|
||||
case CHUNK_TYPE_3:
|
||||
if (needs_ext_ts (meta)) {
|
||||
guint32 timestamp;
|
||||
|
||||
if (size < header_size + 4) {
|
||||
GST_TRACE ("not enough bytes to read extended timestamp");
|
||||
return header_size + 4;
|
||||
}
|
||||
|
||||
GST_TRACE ("Reading extended timestamp");
|
||||
timestamp = GST_READ_UINT32_BE (data + header_size);
|
||||
|
||||
if (type == 3 && meta->ts_delta != timestamp) {
|
||||
GST_WARNING ("Type 3 extended timestamp does not match expected"
|
||||
" timestamp (want %" G_GUINT32_FORMAT " got %" G_GUINT32_FORMAT
|
||||
"); assuming it's not present", meta->ts_delta, timestamp);
|
||||
} else {
|
||||
meta->ts_delta = timestamp;
|
||||
header_size += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GST_MEMDUMP ("<<< chunk header", data, header_size);
|
||||
|
||||
if (!chunk_stream_is_open (cstream)) {
|
||||
GstClockTime dts = GST_BUFFER_DTS (buffer);
|
||||
guint32 delta_32, abs_32;
|
||||
gint64 delta_64;
|
||||
|
||||
if (has_abs_timestamp) {
|
||||
abs_32 = meta->ts_delta;
|
||||
delta_32 = abs_32 - dts / GST_MSECOND;
|
||||
} else {
|
||||
delta_32 = meta->ts_delta;
|
||||
abs_32 = delta_32 + dts / GST_MSECOND;
|
||||
}
|
||||
|
||||
GST_TRACE ("Timestamp delta is %" G_GUINT32_FORMAT " (absolute %"
|
||||
G_GUINT32_FORMAT ")", delta_32, abs_32);
|
||||
|
||||
/* emulate signed overflow */
|
||||
delta_64 = delta_32;
|
||||
if (delta_64 > G_MAXINT32) {
|
||||
delta_64 -= G_MAXUINT32;
|
||||
delta_64 -= 1;
|
||||
}
|
||||
|
||||
delta_64 *= GST_MSECOND;
|
||||
|
||||
if (G_LIKELY (delta_64 >= 0)) {
|
||||
/* Normal advancement */
|
||||
} else if (G_LIKELY ((guint64) (-delta_64) <= dts)) {
|
||||
/* In-bounds regression */
|
||||
GST_WARNING ("Timestamp regression: %" GST_STIME_FORMAT,
|
||||
GST_STIME_ARGS (delta_64));
|
||||
} else {
|
||||
/* Out-of-bounds regression */
|
||||
GST_WARNING ("Timestamp regression: %" GST_STIME_FORMAT ", offsetting",
|
||||
GST_STIME_ARGS (delta_64));
|
||||
delta_64 = delta_32 * GST_MSECOND;
|
||||
}
|
||||
|
||||
GST_BUFFER_DTS (buffer) += delta_64;
|
||||
|
||||
GST_TRACE ("Adjusted buffer DTS (%" GST_TIME_FORMAT ") by %"
|
||||
GST_STIME_FORMAT " to %" GST_TIME_FORMAT, GST_TIME_ARGS (dts),
|
||||
GST_STIME_ARGS (delta_64), GST_TIME_ARGS (GST_BUFFER_DTS (buffer)));
|
||||
} else {
|
||||
GST_TRACE ("Message payload already started; not touching timestamp");
|
||||
}
|
||||
|
||||
return header_size;
|
||||
}
|
||||
|
||||
guint32
|
||||
gst_rtmp_chunk_stream_parse_payload (GstRtmpChunkStream * cstream,
|
||||
guint32 chunk_size, guint8 ** data)
|
||||
{
|
||||
GstMemory *mem;
|
||||
|
||||
g_return_val_if_fail (cstream, 0);
|
||||
g_return_val_if_fail (cstream->buffer, 0);
|
||||
|
||||
if (!chunk_stream_is_open (cstream)) {
|
||||
guint32 size = cstream->meta->size;
|
||||
|
||||
GST_TRACE ("Allocating buffer, payload size %" G_GUINT32_FORMAT, size);
|
||||
|
||||
mem = gst_allocator_alloc (NULL, size, 0);
|
||||
if (!mem) {
|
||||
GST_ERROR ("Failed to allocate buffer for payload size %"
|
||||
G_GUINT32_FORMAT, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gst_buffer_append_memory (cstream->buffer, mem);
|
||||
gst_buffer_map (cstream->buffer, &cstream->map, GST_MAP_WRITE);
|
||||
}
|
||||
|
||||
g_return_val_if_fail (cstream->map.size == cstream->meta->size, 0);
|
||||
|
||||
if (data) {
|
||||
*data = cstream->map.data + cstream->offset;
|
||||
}
|
||||
|
||||
return chunk_stream_next_size (cstream, chunk_size);
|
||||
}
|
||||
|
||||
guint32
|
||||
gst_rtmp_chunk_stream_wrote_payload (GstRtmpChunkStream * cstream,
|
||||
guint32 chunk_size)
|
||||
{
|
||||
guint32 size;
|
||||
|
||||
g_return_val_if_fail (cstream, FALSE);
|
||||
g_return_val_if_fail (chunk_stream_is_open (cstream), FALSE);
|
||||
|
||||
size = chunk_stream_next_size (cstream, chunk_size);
|
||||
cstream->offset += size;
|
||||
cstream->bytes += size;
|
||||
|
||||
return chunk_stream_next_size (cstream, chunk_size);
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_chunk_stream_parse_finish (GstRtmpChunkStream * cstream)
|
||||
{
|
||||
GstBuffer *buffer, *empty;
|
||||
|
||||
g_return_val_if_fail (cstream, NULL);
|
||||
g_return_val_if_fail (cstream->buffer, NULL);
|
||||
|
||||
buffer = gst_buffer_ref (cstream->buffer);
|
||||
GST_BUFFER_OFFSET_END (buffer) = cstream->bytes;
|
||||
|
||||
gst_rtmp_buffer_dump (buffer, "<<< message");
|
||||
|
||||
chunk_stream_clear (cstream);
|
||||
|
||||
empty = gst_buffer_new ();
|
||||
|
||||
if (!gst_buffer_copy_into (empty, buffer, GST_BUFFER_COPY_META, 0, 0)) {
|
||||
GST_ERROR ("copy_into failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GST_BUFFER_DTS (empty) = GST_BUFFER_DTS (buffer);
|
||||
GST_BUFFER_OFFSET (empty) = GST_BUFFER_OFFSET_END (buffer);
|
||||
|
||||
chunk_stream_take_buffer (cstream, empty);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_chunk_stream_serialize_start (GstRtmpChunkStream * cstream,
|
||||
GstBuffer * buffer, guint32 chunk_size)
|
||||
{
|
||||
ChunkType type;
|
||||
|
||||
g_return_val_if_fail (cstream, NULL);
|
||||
g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
|
||||
|
||||
type = select_chunk_type (cstream, buffer);
|
||||
g_return_val_if_fail (type >= 0, NULL);
|
||||
|
||||
GST_TRACE ("Starting serialization of message %" GST_PTR_FORMAT
|
||||
" into stream %" G_GUINT32_FORMAT, buffer, cstream->id);
|
||||
|
||||
gst_rtmp_buffer_dump (buffer, ">>> message");
|
||||
|
||||
chunk_stream_clear (cstream);
|
||||
chunk_stream_take_buffer (cstream, buffer);
|
||||
|
||||
return serialize_next (cstream, chunk_size, type);
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_chunk_stream_serialize_next (GstRtmpChunkStream * cstream,
|
||||
guint32 chunk_size)
|
||||
{
|
||||
g_return_val_if_fail (cstream, NULL);
|
||||
g_return_val_if_fail (cstream->buffer, NULL);
|
||||
|
||||
if (chunk_stream_next_size (cstream, chunk_size) == 0) {
|
||||
GST_TRACE ("Message serialization finished");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GST_TRACE ("Continuing serialization of message %" GST_PTR_FORMAT
|
||||
" into stream %" G_GUINT32_FORMAT, cstream->buffer, cstream->id);
|
||||
|
||||
return serialize_next (cstream, chunk_size, CHUNK_TYPE_3);
|
||||
}
|
||||
|
||||
GstRtmpChunkStreams *
|
||||
gst_rtmp_chunk_streams_new (void)
|
||||
{
|
||||
GstRtmpChunkStreams *cstreams;
|
||||
|
||||
init_debug ();
|
||||
|
||||
cstreams = g_slice_new (GstRtmpChunkStreams);
|
||||
cstreams->array = g_array_new (FALSE, TRUE, sizeof (GstRtmpChunkStream));
|
||||
g_array_set_clear_func (cstreams->array,
|
||||
(GDestroyNotify) gst_rtmp_chunk_stream_clear);
|
||||
return cstreams;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_chunk_streams_free (gpointer ptr)
|
||||
{
|
||||
GstRtmpChunkStreams *cstreams = ptr;
|
||||
g_clear_pointer (&cstreams->array, g_array_unref);
|
||||
g_slice_free (GstRtmpChunkStreams, cstreams);
|
||||
}
|
||||
|
||||
GstRtmpChunkStream *
|
||||
gst_rtmp_chunk_streams_get (GstRtmpChunkStreams * cstreams, guint32 id)
|
||||
{
|
||||
GArray *array;
|
||||
GstRtmpChunkStream *entry;
|
||||
guint i;
|
||||
|
||||
g_return_val_if_fail (cstreams, NULL);
|
||||
g_return_val_if_fail (id > CHUNK_BYTE_THREEBYTE, NULL);
|
||||
g_return_val_if_fail (id <= CHUNK_STREAM_MAX_THREEBYTE, NULL);
|
||||
|
||||
array = cstreams->array;
|
||||
|
||||
for (i = 0; i < array->len; i++) {
|
||||
entry = &g_array_index (array, GstRtmpChunkStream, i);
|
||||
if (entry->id == id) {
|
||||
GST_TRACE ("Obtaining chunk stream %" G_GUINT32_FORMAT, id);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
GST_DEBUG ("Allocating chunk stream %" G_GUINT32_FORMAT, id);
|
||||
|
||||
g_array_set_size (array, i + 1);
|
||||
entry = &g_array_index (array, GstRtmpChunkStream, i);
|
||||
entry->id = id;
|
||||
return entry;
|
||||
}
|
59
gst/rtmp2/rtmp/rtmpchunkstream.h
Normal file
59
gst/rtmp2/rtmp/rtmpchunkstream.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP_CHUNK_STREAM_H_
|
||||
#define _GST_RTMP_CHUNK_STREAM_H_
|
||||
|
||||
#include "rtmpmessage.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_RTMP_DEFAULT_CHUNK_SIZE 128
|
||||
#define GST_RTMP_CHUNK_STREAM_PROTOCOL 2
|
||||
|
||||
typedef struct _GstRtmpChunkStream GstRtmpChunkStream;
|
||||
typedef struct _GstRtmpChunkStreams GstRtmpChunkStreams;
|
||||
|
||||
void gst_rtmp_chunk_stream_clear (GstRtmpChunkStream * cstream);
|
||||
|
||||
guint32 gst_rtmp_chunk_stream_parse_id (const guint8 * data, gsize size);
|
||||
guint32 gst_rtmp_chunk_stream_parse_header (GstRtmpChunkStream * cstream,
|
||||
const guint8 * data, gsize size);
|
||||
guint32 gst_rtmp_chunk_stream_parse_payload (GstRtmpChunkStream * cstream,
|
||||
guint32 chunk_size, guint8 ** data);
|
||||
guint32 gst_rtmp_chunk_stream_wrote_payload (GstRtmpChunkStream * cstream,
|
||||
guint32 chunk_size);
|
||||
GstBuffer * gst_rtmp_chunk_stream_parse_finish (GstRtmpChunkStream * cstream);
|
||||
|
||||
GstBuffer * gst_rtmp_chunk_stream_serialize_start (GstRtmpChunkStream * cstream,
|
||||
GstBuffer * buffer, guint32 chunk_size);
|
||||
GstBuffer * gst_rtmp_chunk_stream_serialize_next (GstRtmpChunkStream * cstream,
|
||||
guint32 chunk_size);
|
||||
|
||||
GstRtmpChunkStreams * gst_rtmp_chunk_streams_new (void);
|
||||
void gst_rtmp_chunk_streams_free (gpointer ptr);
|
||||
|
||||
GstRtmpChunkStream *
|
||||
gst_rtmp_chunk_streams_get (GstRtmpChunkStreams * cstreams, guint32 id);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
1240
gst/rtmp2/rtmp/rtmpclient.c
Normal file
1240
gst/rtmp2/rtmp/rtmpclient.c
Normal file
File diff suppressed because it is too large
Load diff
101
gst/rtmp2/rtmp/rtmpclient.h
Normal file
101
gst/rtmp2/rtmp/rtmpclient.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP_CLIENT_H_
|
||||
#define _GST_RTMP_CLIENT_H_
|
||||
|
||||
#include "rtmpconnection.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_RTMP_SCHEME (gst_rtmp_scheme_get_type ())
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_RTMP_SCHEME_RTMP = 0,
|
||||
GST_RTMP_SCHEME_RTMPS,
|
||||
} GstRtmpScheme;
|
||||
|
||||
GType gst_rtmp_scheme_get_type (void);
|
||||
|
||||
GstRtmpScheme gst_rtmp_scheme_from_string (const gchar * string);
|
||||
GstRtmpScheme gst_rtmp_scheme_from_uri (const GstUri * uri);
|
||||
const gchar * gst_rtmp_scheme_to_string (GstRtmpScheme scheme);
|
||||
const gchar * const * gst_rtmp_scheme_get_strings (void);
|
||||
guint gst_rtmp_scheme_get_default_port (GstRtmpScheme scheme);
|
||||
|
||||
|
||||
|
||||
#define GST_TYPE_RTMP_AUTHMOD (gst_rtmp_authmod_get_type ())
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_RTMP_AUTHMOD_NONE = 0,
|
||||
GST_RTMP_AUTHMOD_AUTO,
|
||||
GST_RTMP_AUTHMOD_ADOBE,
|
||||
} GstRtmpAuthmod;
|
||||
|
||||
GType gst_rtmp_authmod_get_type (void);
|
||||
|
||||
|
||||
|
||||
typedef struct _GstRtmpLocation
|
||||
{
|
||||
GstRtmpScheme scheme;
|
||||
gchar *host;
|
||||
guint port;
|
||||
gchar *application;
|
||||
gchar *stream;
|
||||
gchar *username;
|
||||
gchar *password;
|
||||
gchar *secure_token;
|
||||
GstRtmpAuthmod authmod;
|
||||
gint timeout;
|
||||
GTlsCertificateFlags tls_flags;
|
||||
gchar *flash_ver;
|
||||
} GstRtmpLocation;
|
||||
|
||||
void gst_rtmp_location_copy (GstRtmpLocation * dest,
|
||||
const GstRtmpLocation * src);
|
||||
void gst_rtmp_location_clear (GstRtmpLocation * uri);
|
||||
gchar *gst_rtmp_location_get_string (const GstRtmpLocation * location,
|
||||
gboolean with_stream);
|
||||
|
||||
|
||||
|
||||
void gst_rtmp_client_connect_async (const GstRtmpLocation * location,
|
||||
GCancellable * cancellable, GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GstRtmpConnection *gst_rtmp_client_connect_finish (GAsyncResult * result,
|
||||
GError ** error);
|
||||
void gst_rtmp_client_start_publish_async (GstRtmpConnection * connection,
|
||||
const gchar * stream, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data);
|
||||
gboolean gst_rtmp_client_start_publish_finish (GstRtmpConnection * connection,
|
||||
GAsyncResult * result, guint * stream_id, GError ** error);
|
||||
|
||||
void gst_rtmp_client_start_play_async (GstRtmpConnection * connection,
|
||||
const gchar * stream, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data);
|
||||
gboolean gst_rtmp_client_start_play_finish (GstRtmpConnection * connection,
|
||||
GAsyncResult * result, guint * stream_id, GError ** error);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
996
gst/rtmp2/rtmp/rtmpconnection.c
Normal file
996
gst/rtmp2/rtmp/rtmpconnection.c
Normal file
|
@ -0,0 +1,996 @@
|
|||
/* GStreamer RTMP Library
|
||||
* Copyright (C) 2013 David Schleef <ds@schleef.org>
|
||||
* 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 Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "rtmpconnection.h"
|
||||
#include "rtmpchunkstream.h"
|
||||
#include "rtmpmessage.h"
|
||||
#include "rtmputils.h"
|
||||
#include "amf.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_rtmp_connection_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_rtmp_connection_debug_category
|
||||
|
||||
#define READ_SIZE 8192
|
||||
|
||||
typedef void (*GstRtmpConnectionCallback) (GstRtmpConnection * connection);
|
||||
|
||||
struct _GstRtmpConnection
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
/* should be properties */
|
||||
gboolean input_paused;
|
||||
gboolean error;
|
||||
|
||||
/* private */
|
||||
GThread *thread;
|
||||
GSocketConnection *connection;
|
||||
GCancellable *cancellable;
|
||||
GSocketClient *socket_client;
|
||||
GAsyncQueue *output_queue;
|
||||
GMainContext *main_context;
|
||||
|
||||
GSource *input_source;
|
||||
GByteArray *input_bytes;
|
||||
guint input_needed_bytes;
|
||||
GstRtmpChunkStreams *input_streams, *output_streams;
|
||||
GList *transactions;
|
||||
GList *expected_commands;
|
||||
guint transaction_count;
|
||||
|
||||
GstRtmpConnectionMessageFunc input_handler;
|
||||
gpointer input_handler_user_data;
|
||||
GDestroyNotify input_handler_user_data_destroy;
|
||||
|
||||
GstRtmpConnectionFunc output_handler;
|
||||
gpointer output_handler_user_data;
|
||||
GDestroyNotify output_handler_user_data_destroy;
|
||||
|
||||
gboolean writing;
|
||||
|
||||
/* RTMP configuration */
|
||||
gsize in_chunk_size;
|
||||
gsize out_chunk_size;
|
||||
gsize in_window_ack_size;
|
||||
gsize out_window_ack_size;
|
||||
gsize total_input_bytes;
|
||||
gsize bytes_since_ack;
|
||||
};
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
} GstRtmpConnectionClass;
|
||||
|
||||
/* prototypes */
|
||||
|
||||
static void gst_rtmp_connection_dispose (GObject * object);
|
||||
static void gst_rtmp_connection_finalize (GObject * object);
|
||||
static void gst_rtmp_connection_emit_error (GstRtmpConnection * self);
|
||||
static gboolean gst_rtmp_connection_input_ready (GInputStream * is,
|
||||
gpointer user_data);
|
||||
static void gst_rtmp_connection_start_write (GstRtmpConnection * self);
|
||||
static void gst_rtmp_connection_write_bytes_done (GObject * obj,
|
||||
GAsyncResult * result, gpointer user_data);
|
||||
static void gst_rtmp_connection_start_read (GstRtmpConnection * sc,
|
||||
guint needed_bytes);
|
||||
static void gst_rtmp_connection_try_read (GstRtmpConnection * sc);
|
||||
static void gst_rtmp_connection_do_read (GstRtmpConnection * sc);
|
||||
static void gst_rtmp_connection_handle_protocol_control (GstRtmpConnection *
|
||||
connection, GstBuffer * buffer);
|
||||
static void gst_rtmp_connection_handle_cm (GstRtmpConnection * connection,
|
||||
GstBuffer * buffer);
|
||||
static void gst_rtmp_connection_handle_user_control (GstRtmpConnection * sc,
|
||||
GstBuffer * buffer);
|
||||
static void gst_rtmp_connection_handle_message (GstRtmpConnection * sc,
|
||||
GstBuffer * buffer);
|
||||
|
||||
static void gst_rtmp_connection_send_ack (GstRtmpConnection * connection);
|
||||
static void
|
||||
gst_rtmp_connection_send_ping_response (GstRtmpConnection * connection,
|
||||
guint32 event_data);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gdouble transaction_id;
|
||||
GstRtmpCommandCallback func;
|
||||
gpointer user_data;
|
||||
} Transaction;
|
||||
|
||||
static Transaction *
|
||||
transaction_new (gdouble transaction_id, GstRtmpCommandCallback func,
|
||||
gpointer user_data)
|
||||
{
|
||||
Transaction *data = g_slice_new (Transaction);
|
||||
data->transaction_id = transaction_id;
|
||||
data->func = func;
|
||||
data->user_data = user_data;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
transaction_free (gpointer ptr)
|
||||
{
|
||||
Transaction *data = ptr;
|
||||
g_slice_free (Transaction, data);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint32 stream_id;
|
||||
gchar *command_name;
|
||||
GstRtmpCommandCallback func;
|
||||
gpointer user_data;
|
||||
} ExpectedCommand;
|
||||
|
||||
static ExpectedCommand *
|
||||
expected_command_new (guint32 stream_id, const gchar * command_name,
|
||||
GstRtmpCommandCallback func, gpointer user_data)
|
||||
{
|
||||
ExpectedCommand *data = g_slice_new (ExpectedCommand);
|
||||
data->stream_id = stream_id;
|
||||
data->command_name = g_strdup (command_name);
|
||||
data->func = func;
|
||||
data->user_data = user_data;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
expected_command_free (gpointer ptr)
|
||||
{
|
||||
ExpectedCommand *data = ptr;
|
||||
g_free (data->command_name);
|
||||
g_slice_free (ExpectedCommand, data);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
SIGNAL_ERROR,
|
||||
SIGNAL_STREAM_CONTROL,
|
||||
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals[N_SIGNALS] = { 0, };
|
||||
|
||||
/* class initialization */
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GstRtmpConnection, gst_rtmp_connection,
|
||||
G_TYPE_OBJECT,
|
||||
GST_DEBUG_CATEGORY_INIT (gst_rtmp_connection_debug_category,
|
||||
"rtmpconnection", 0, "debug category for GstRtmpConnection class"));
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_class_init (GstRtmpConnectionClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = gst_rtmp_connection_dispose;
|
||||
gobject_class->finalize = gst_rtmp_connection_finalize;
|
||||
|
||||
signals[SIGNAL_ERROR] = g_signal_new ("error", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
signals[SIGNAL_STREAM_CONTROL] = g_signal_new ("stream-control",
|
||||
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_UINT);
|
||||
|
||||
GST_DEBUG_REGISTER_FUNCPTR (gst_rtmp_connection_do_read);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_init (GstRtmpConnection * rtmpconnection)
|
||||
{
|
||||
rtmpconnection->cancellable = g_cancellable_new ();
|
||||
rtmpconnection->output_queue =
|
||||
g_async_queue_new_full ((GDestroyNotify) g_bytes_unref);
|
||||
rtmpconnection->input_streams = gst_rtmp_chunk_streams_new ();
|
||||
rtmpconnection->output_streams = gst_rtmp_chunk_streams_new ();
|
||||
|
||||
rtmpconnection->in_chunk_size = GST_RTMP_DEFAULT_CHUNK_SIZE;
|
||||
rtmpconnection->out_chunk_size = GST_RTMP_DEFAULT_CHUNK_SIZE;
|
||||
|
||||
rtmpconnection->input_bytes = g_byte_array_sized_new (2 * READ_SIZE);
|
||||
rtmpconnection->input_needed_bytes = 1;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_dispose (GObject * object)
|
||||
{
|
||||
GstRtmpConnection *rtmpconnection = GST_RTMP_CONNECTION (object);
|
||||
GST_DEBUG_OBJECT (rtmpconnection, "dispose");
|
||||
|
||||
/* clean up as possible. may be called multiple times */
|
||||
|
||||
gst_rtmp_connection_close (rtmpconnection);
|
||||
g_cancellable_cancel (rtmpconnection->cancellable);
|
||||
gst_rtmp_connection_set_input_handler (rtmpconnection, NULL, NULL, NULL);
|
||||
gst_rtmp_connection_set_output_handler (rtmpconnection, NULL, NULL, NULL);
|
||||
|
||||
G_OBJECT_CLASS (gst_rtmp_connection_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_finalize (GObject * object)
|
||||
{
|
||||
GstRtmpConnection *rtmpconnection = GST_RTMP_CONNECTION (object);
|
||||
GST_DEBUG_OBJECT (rtmpconnection, "finalize");
|
||||
|
||||
/* clean up object here */
|
||||
|
||||
g_clear_object (&rtmpconnection->cancellable);
|
||||
g_clear_object (&rtmpconnection->connection);
|
||||
g_clear_pointer (&rtmpconnection->output_queue, g_async_queue_unref);
|
||||
g_clear_pointer (&rtmpconnection->input_streams, gst_rtmp_chunk_streams_free);
|
||||
g_clear_pointer (&rtmpconnection->output_streams,
|
||||
gst_rtmp_chunk_streams_free);
|
||||
g_clear_pointer (&rtmpconnection->input_bytes, g_byte_array_unref);
|
||||
g_clear_pointer (&rtmpconnection->main_context, g_main_context_unref);
|
||||
g_clear_pointer (&rtmpconnection->thread, g_thread_unref);
|
||||
|
||||
G_OBJECT_CLASS (gst_rtmp_connection_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
GSocket *
|
||||
gst_rtmp_connection_get_socket (GstRtmpConnection * sc)
|
||||
{
|
||||
return g_socket_connection_get_socket (sc->connection);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_set_socket_connection (GstRtmpConnection * sc,
|
||||
GSocketConnection * connection)
|
||||
{
|
||||
GInputStream *is;
|
||||
|
||||
sc->thread = g_thread_ref (g_thread_self ());
|
||||
sc->main_context = g_main_context_ref_thread_default ();
|
||||
sc->connection = g_object_ref (connection);
|
||||
|
||||
/* refs the socket because it's creating an input stream, which holds a ref */
|
||||
is = g_io_stream_get_input_stream (G_IO_STREAM (sc->connection));
|
||||
/* refs the socket because it's creating a socket source */
|
||||
g_warn_if_fail (!sc->input_source);
|
||||
sc->input_source =
|
||||
g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (is),
|
||||
sc->cancellable);
|
||||
g_source_set_callback (sc->input_source,
|
||||
(GSourceFunc) gst_rtmp_connection_input_ready, g_object_ref (sc),
|
||||
g_object_unref);
|
||||
g_source_attach (sc->input_source, sc->main_context);
|
||||
}
|
||||
|
||||
GstRtmpConnection *
|
||||
gst_rtmp_connection_new (GSocketConnection * connection)
|
||||
{
|
||||
GstRtmpConnection *sc;
|
||||
|
||||
sc = g_object_new (GST_TYPE_RTMP_CONNECTION, NULL);
|
||||
|
||||
gst_rtmp_connection_set_socket_connection (sc, connection);
|
||||
|
||||
return sc;
|
||||
}
|
||||
|
||||
static void
|
||||
cancel_all_commands (GstRtmpConnection * self)
|
||||
{
|
||||
GList *l;
|
||||
|
||||
for (l = self->transactions; l; l = g_list_next (l)) {
|
||||
Transaction *cc = l->data;
|
||||
GST_LOG ("calling transaction callback %s",
|
||||
GST_DEBUG_FUNCPTR_NAME (cc->func));
|
||||
cc->func ("<cancelled>", NULL, cc->user_data);
|
||||
}
|
||||
g_list_free_full (self->transactions, transaction_free);
|
||||
self->transactions = NULL;
|
||||
|
||||
for (l = self->expected_commands; l; l = g_list_next (l)) {
|
||||
ExpectedCommand *cc = l->data;
|
||||
GST_LOG ("calling expected command callback %s",
|
||||
GST_DEBUG_FUNCPTR_NAME (cc->func));
|
||||
cc->func ("<cancelled>", NULL, cc->user_data);
|
||||
}
|
||||
g_list_free_full (self->expected_commands, expected_command_free);
|
||||
self->expected_commands = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_close (GstRtmpConnection * self)
|
||||
{
|
||||
if (self->thread != g_thread_self ()) {
|
||||
GST_ERROR ("Called from wrong thread");
|
||||
}
|
||||
|
||||
g_cancellable_cancel (self->cancellable);
|
||||
cancel_all_commands (self);
|
||||
|
||||
if (self->input_source) {
|
||||
g_source_destroy (self->input_source);
|
||||
g_clear_pointer (&self->input_source, g_source_unref);
|
||||
}
|
||||
|
||||
if (self->connection) {
|
||||
g_io_stream_close_async (G_IO_STREAM (self->connection),
|
||||
G_PRIORITY_DEFAULT, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_close_and_unref (gpointer ptr)
|
||||
{
|
||||
GstRtmpConnection *connection;
|
||||
|
||||
g_return_if_fail (ptr);
|
||||
|
||||
connection = GST_RTMP_CONNECTION (ptr);
|
||||
gst_rtmp_connection_close (connection);
|
||||
g_object_unref (connection);
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_set_input_handler (GstRtmpConnection * sc,
|
||||
GstRtmpConnectionMessageFunc callback, gpointer user_data,
|
||||
GDestroyNotify user_data_destroy)
|
||||
{
|
||||
if (sc->input_handler_user_data_destroy) {
|
||||
sc->input_handler_user_data_destroy (sc->input_handler_user_data);
|
||||
}
|
||||
|
||||
sc->input_handler = callback;
|
||||
sc->input_handler_user_data = user_data;
|
||||
sc->input_handler_user_data_destroy = user_data_destroy;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_set_output_handler (GstRtmpConnection * sc,
|
||||
GstRtmpConnectionFunc callback, gpointer user_data,
|
||||
GDestroyNotify user_data_destroy)
|
||||
{
|
||||
if (sc->output_handler_user_data_destroy) {
|
||||
sc->output_handler_user_data_destroy (sc->output_handler_user_data);
|
||||
}
|
||||
|
||||
sc->output_handler = callback;
|
||||
sc->output_handler_user_data = user_data;
|
||||
sc->output_handler_user_data_destroy = user_data_destroy;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp_connection_input_ready (GInputStream * is, gpointer user_data)
|
||||
{
|
||||
GstRtmpConnection *sc = user_data;
|
||||
gssize ret;
|
||||
guint oldsize;
|
||||
GError *error = NULL;
|
||||
|
||||
GST_TRACE ("input ready");
|
||||
|
||||
oldsize = sc->input_bytes->len;
|
||||
g_byte_array_set_size (sc->input_bytes, oldsize + READ_SIZE);
|
||||
ret =
|
||||
g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (is),
|
||||
sc->input_bytes->data + oldsize, READ_SIZE, sc->cancellable, &error);
|
||||
g_byte_array_set_size (sc->input_bytes, oldsize + (ret > 0 ? ret : 0));
|
||||
|
||||
if (ret < 0) {
|
||||
gint code = error->code;
|
||||
|
||||
if (error->domain == G_IO_ERROR && (code == G_IO_ERROR_WOULD_BLOCK ||
|
||||
code == G_IO_ERROR_TIMED_OUT || code == G_IO_ERROR_AGAIN)) {
|
||||
/* should retry */
|
||||
GST_DEBUG ("read IO error %d %s, continuing", code, error->message);
|
||||
g_error_free (error);
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
GST_ERROR ("read error: %s %d %s", g_quark_to_string (error->domain),
|
||||
code, error->message);
|
||||
g_error_free (error);
|
||||
} else if (ret == 0) {
|
||||
GST_INFO ("read EOF");
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
gst_rtmp_connection_emit_error (sc);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
GST_TRACE ("read %" G_GSIZE_FORMAT " bytes", ret);
|
||||
|
||||
sc->total_input_bytes += ret;
|
||||
sc->bytes_since_ack += ret;
|
||||
if (sc->bytes_since_ack >= sc->in_window_ack_size) {
|
||||
gst_rtmp_connection_send_ack (sc);
|
||||
}
|
||||
|
||||
gst_rtmp_connection_try_read (sc);
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_start_write (GstRtmpConnection * self)
|
||||
{
|
||||
GOutputStream *os;
|
||||
GBytes *bytes;
|
||||
|
||||
if (self->writing) {
|
||||
return;
|
||||
}
|
||||
|
||||
bytes = g_async_queue_try_pop (self->output_queue);
|
||||
if (!bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->writing = TRUE;
|
||||
if (self->output_handler) {
|
||||
self->output_handler (self, self->output_handler_user_data);
|
||||
}
|
||||
|
||||
os = g_io_stream_get_output_stream (G_IO_STREAM (self->connection));
|
||||
gst_rtmp_output_stream_write_all_bytes_async (os, bytes,
|
||||
G_PRIORITY_DEFAULT, self->cancellable,
|
||||
gst_rtmp_connection_write_bytes_done, g_object_ref (self));
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_emit_error (GstRtmpConnection * self)
|
||||
{
|
||||
if (self->error) {
|
||||
return;
|
||||
}
|
||||
|
||||
GST_INFO ("connection error");
|
||||
self->error = TRUE;
|
||||
|
||||
cancel_all_commands (self);
|
||||
|
||||
g_signal_emit (self, signals[SIGNAL_ERROR], 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_write_bytes_done (GObject * obj,
|
||||
GAsyncResult * result, gpointer user_data)
|
||||
{
|
||||
GOutputStream *os = G_OUTPUT_STREAM (obj);
|
||||
GstRtmpConnection *self = GST_RTMP_CONNECTION (user_data);
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
|
||||
self->writing = FALSE;
|
||||
|
||||
res = gst_rtmp_output_stream_write_all_bytes_finish (os, result, &error);
|
||||
if (!res) {
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
GST_INFO ("write cancelled");
|
||||
} else {
|
||||
GST_ERROR ("write error: %s", error->message);
|
||||
}
|
||||
gst_rtmp_connection_emit_error (self);
|
||||
g_error_free (error);
|
||||
g_object_unref (self);
|
||||
return;
|
||||
}
|
||||
|
||||
GST_LOG ("write completed");
|
||||
gst_rtmp_connection_start_write (self);
|
||||
g_object_unref (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_start_read (GstRtmpConnection * connection,
|
||||
guint needed_bytes)
|
||||
{
|
||||
g_return_if_fail (needed_bytes > 0);
|
||||
connection->input_needed_bytes = needed_bytes;
|
||||
gst_rtmp_connection_try_read (connection);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_try_read (GstRtmpConnection * connection)
|
||||
{
|
||||
guint need = connection->input_needed_bytes,
|
||||
len = connection->input_bytes->len;
|
||||
|
||||
if (len < need) {
|
||||
GST_TRACE ("got %u < %u bytes, need more", len, need);
|
||||
return;
|
||||
}
|
||||
|
||||
GST_TRACE ("got %u >= %u bytes, proceeding", len, need);
|
||||
gst_rtmp_connection_do_read (connection);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_take_input_bytes (GstRtmpConnection * sc, gsize size,
|
||||
GBytes ** outbytes)
|
||||
{
|
||||
g_return_if_fail (size <= sc->input_bytes->len);
|
||||
|
||||
if (outbytes) {
|
||||
*outbytes = g_bytes_new (sc->input_bytes->data, size);
|
||||
}
|
||||
|
||||
g_byte_array_remove_range (sc->input_bytes, 0, size);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_do_read (GstRtmpConnection * sc)
|
||||
{
|
||||
GByteArray *input_bytes = sc->input_bytes;
|
||||
gsize needed_bytes = 1;
|
||||
|
||||
while (1) {
|
||||
GstRtmpChunkStream *cstream;
|
||||
guint32 chunk_stream_id, header_size, next_size;
|
||||
guint8 *data;
|
||||
|
||||
chunk_stream_id = gst_rtmp_chunk_stream_parse_id (input_bytes->data,
|
||||
input_bytes->len);
|
||||
|
||||
if (!chunk_stream_id) {
|
||||
needed_bytes = input_bytes->len + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
cstream = gst_rtmp_chunk_streams_get (sc->input_streams, chunk_stream_id);
|
||||
header_size = gst_rtmp_chunk_stream_parse_header (cstream,
|
||||
input_bytes->data, input_bytes->len);
|
||||
|
||||
if (input_bytes->len < header_size) {
|
||||
needed_bytes = header_size;
|
||||
break;
|
||||
}
|
||||
|
||||
next_size = gst_rtmp_chunk_stream_parse_payload (cstream,
|
||||
sc->in_chunk_size, &data);
|
||||
|
||||
if (input_bytes->len < header_size + next_size) {
|
||||
needed_bytes = header_size + next_size;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy (data, input_bytes->data + header_size, next_size);
|
||||
gst_rtmp_connection_take_input_bytes (sc, header_size + next_size, NULL);
|
||||
|
||||
next_size = gst_rtmp_chunk_stream_wrote_payload (cstream,
|
||||
sc->in_chunk_size);
|
||||
|
||||
if (next_size == 0) {
|
||||
GstBuffer *buffer = gst_rtmp_chunk_stream_parse_finish (cstream);
|
||||
gst_rtmp_connection_handle_message (sc, buffer);
|
||||
gst_buffer_unref (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
gst_rtmp_connection_start_read (sc, needed_bytes);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_handle_message (GstRtmpConnection * sc, GstBuffer * buffer)
|
||||
{
|
||||
if (gst_rtmp_message_is_protocol_control (buffer)) {
|
||||
gst_rtmp_connection_handle_protocol_control (sc, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gst_rtmp_message_is_user_control (buffer)) {
|
||||
gst_rtmp_connection_handle_user_control (sc, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (gst_rtmp_message_get_type (buffer)) {
|
||||
case GST_RTMP_MESSAGE_TYPE_COMMAND_AMF0:
|
||||
gst_rtmp_connection_handle_cm (sc, buffer);
|
||||
return;
|
||||
|
||||
default:
|
||||
if (sc->input_handler) {
|
||||
sc->input_handler (sc, buffer, sc->input_handler_user_data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_handle_protocol_control (GstRtmpConnection * connection,
|
||||
GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpProtocolControl pc;
|
||||
|
||||
if (!gst_rtmp_message_parse_protocol_control (buffer, &pc)) {
|
||||
GST_ERROR ("can't parse protocol control message");
|
||||
return;
|
||||
}
|
||||
|
||||
GST_LOG ("got protocol control message %d:%s", pc.type,
|
||||
gst_rtmp_message_type_get_nick (pc.type));
|
||||
|
||||
switch (pc.type) {
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_CHUNK_SIZE:
|
||||
GST_INFO ("new chunk size %" G_GUINT32_FORMAT, pc.param);
|
||||
connection->in_chunk_size = pc.param;
|
||||
break;
|
||||
|
||||
case GST_RTMP_MESSAGE_TYPE_ABORT_MESSAGE:
|
||||
GST_ERROR ("unimplemented: chunk abort, stream_id = %" G_GUINT32_FORMAT,
|
||||
pc.param);
|
||||
break;
|
||||
|
||||
case GST_RTMP_MESSAGE_TYPE_ACKNOWLEDGEMENT:
|
||||
/* We don't really send ack requests that we care about, so ignore */
|
||||
GST_DEBUG ("acknowledgement %" G_GUINT32_FORMAT, pc.param);
|
||||
break;
|
||||
|
||||
case GST_RTMP_MESSAGE_TYPE_WINDOW_ACK_SIZE:
|
||||
GST_INFO ("window ack size: %" G_GUINT32_FORMAT, pc.param);
|
||||
connection->in_window_ack_size = pc.param;
|
||||
break;
|
||||
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_PEER_BANDWIDTH:
|
||||
GST_FIXME ("set peer bandwidth: %" G_GUINT32_FORMAT ", %"
|
||||
G_GUINT32_FORMAT, pc.param, pc.param2);
|
||||
/* FIXME this is not correct, but close enough */
|
||||
gst_rtmp_connection_request_window_size (connection, pc.param);
|
||||
break;
|
||||
|
||||
default:
|
||||
GST_ERROR ("unimplemented protocol control type %d:%s", pc.type,
|
||||
gst_rtmp_message_type_get_nick (pc.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_handle_user_control (GstRtmpConnection * connection,
|
||||
GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpUserControl uc;
|
||||
|
||||
if (!gst_rtmp_message_parse_user_control (buffer, &uc)) {
|
||||
GST_ERROR ("can't parse user control message");
|
||||
return;
|
||||
}
|
||||
|
||||
GST_LOG ("got user control message %d:%s", uc.type,
|
||||
gst_rtmp_user_control_type_get_nick (uc.type));
|
||||
|
||||
switch (uc.type) {
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_BEGIN:
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_EOF:
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_DRY:
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_IS_RECORDED:
|
||||
GST_INFO ("stream %u got %s", uc.param,
|
||||
gst_rtmp_user_control_type_get_nick (uc.type));
|
||||
g_signal_emit (connection, signals[SIGNAL_STREAM_CONTROL], 0,
|
||||
uc.type, uc.param);
|
||||
break;
|
||||
|
||||
case GST_RTMP_USER_CONTROL_TYPE_SET_BUFFER_LENGTH:
|
||||
GST_FIXME ("ignoring set buffer length: %" G_GUINT32_FORMAT ", %"
|
||||
G_GUINT32_FORMAT " ms", uc.param, uc.param2);
|
||||
break;
|
||||
|
||||
case GST_RTMP_USER_CONTROL_TYPE_PING_REQUEST:
|
||||
GST_DEBUG ("ping request: %" G_GUINT32_FORMAT, uc.param);
|
||||
gst_rtmp_connection_send_ping_response (connection, uc.param);
|
||||
break;
|
||||
|
||||
case GST_RTMP_USER_CONTROL_TYPE_PING_RESPONSE:
|
||||
GST_DEBUG ("ignoring ping response: %" G_GUINT32_FORMAT, uc.param);
|
||||
break;
|
||||
|
||||
case GST_RTMP_USER_CONTROL_TYPE_BUFFER_EMPTY:
|
||||
GST_LOG ("ignoring buffer empty: %" G_GUINT32_FORMAT, uc.param);
|
||||
break;
|
||||
|
||||
case GST_RTMP_USER_CONTROL_TYPE_BUFFER_READY:
|
||||
GST_LOG ("ignoring buffer ready: %" G_GUINT32_FORMAT, uc.param);
|
||||
break;
|
||||
|
||||
default:
|
||||
GST_ERROR ("unimplemented user control type %d:%s", uc.type,
|
||||
gst_rtmp_user_control_type_get_nick (uc.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
is_command_response (const gchar * command_name)
|
||||
{
|
||||
return g_strcmp0 (command_name, "_result") == 0 ||
|
||||
g_strcmp0 (command_name, "_error") == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_handle_cm (GstRtmpConnection * sc, GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
gchar *command_name;
|
||||
gdouble transaction_id;
|
||||
GPtrArray *args;
|
||||
|
||||
{
|
||||
GstMapInfo map;
|
||||
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
||||
args = gst_amf_parse_command (map.data, map.size, &transaction_id,
|
||||
&command_name);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
}
|
||||
|
||||
if (!args) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isfinite (transaction_id) || transaction_id < 0 ||
|
||||
transaction_id > G_MAXUINT) {
|
||||
GST_WARNING ("Server sent command \"%s\" with extreme transaction ID %.0f",
|
||||
GST_STR_NULL (command_name), transaction_id);
|
||||
} else if (transaction_id > sc->transaction_count) {
|
||||
GST_WARNING ("Server sent command \"%s\" with unused transaction ID "
|
||||
"(%.0f > %u)", GST_STR_NULL (command_name), transaction_id,
|
||||
sc->transaction_count);
|
||||
sc->transaction_count = transaction_id;
|
||||
}
|
||||
|
||||
GST_DEBUG ("got control message \"%s\" transaction %.0f size %"
|
||||
G_GUINT32_FORMAT, GST_STR_NULL (command_name), transaction_id,
|
||||
meta->size);
|
||||
|
||||
if (is_command_response (command_name)) {
|
||||
if (transaction_id != 0) {
|
||||
GList *l;
|
||||
|
||||
for (l = sc->transactions; l; l = g_list_next (l)) {
|
||||
Transaction *t = l->data;
|
||||
|
||||
if (t->transaction_id != transaction_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GST_LOG ("calling transaction callback %s",
|
||||
GST_DEBUG_FUNCPTR_NAME (t->func));
|
||||
sc->transactions = g_list_remove_link (sc->transactions, l);
|
||||
t->func (command_name, args, t->user_data);
|
||||
g_list_free_full (l, transaction_free);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
GST_WARNING ("Server sent response \"%s\" without transaction",
|
||||
GST_STR_NULL (command_name));
|
||||
}
|
||||
} else {
|
||||
GList *l;
|
||||
|
||||
if (transaction_id != 0) {
|
||||
GST_FIXME ("Server sent command \"%s\" expecting reply",
|
||||
GST_STR_NULL (command_name));
|
||||
}
|
||||
|
||||
for (l = sc->expected_commands; l; l = g_list_next (l)) {
|
||||
ExpectedCommand *ec = l->data;
|
||||
|
||||
if (ec->stream_id != meta->mstream) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g_strcmp0 (ec->command_name, command_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GST_LOG ("calling expected command callback %s",
|
||||
GST_DEBUG_FUNCPTR_NAME (ec->func));
|
||||
sc->expected_commands = g_list_remove_link (sc->expected_commands, l);
|
||||
ec->func (command_name, args, ec->user_data);
|
||||
g_list_free_full (l, expected_command_free);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_free (command_name);
|
||||
g_ptr_array_unref (args);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
start_write (gpointer user_data)
|
||||
{
|
||||
GstRtmpConnection *sc = user_data;
|
||||
gst_rtmp_connection_start_write (sc);
|
||||
g_object_unref (sc);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
byte_array_take_buffer (GByteArray * byte_array, GstBuffer * buffer)
|
||||
{
|
||||
GstMapInfo map;
|
||||
gboolean ret;
|
||||
ret = gst_buffer_map (buffer, &map, GST_MAP_READ);
|
||||
g_assert (ret);
|
||||
g_assert (byte_array->len + map.size <= (guint64) G_MAXUINT);
|
||||
g_byte_array_append (byte_array, map.data, map.size);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
gst_buffer_unref (buffer);
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_queue_message (GstRtmpConnection * self, GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta;
|
||||
GstRtmpChunkStream *cstream;
|
||||
GstBuffer *out_buffer;
|
||||
GByteArray *out_ba;
|
||||
|
||||
g_return_if_fail (GST_IS_RTMP_CONNECTION (self));
|
||||
g_return_if_fail (GST_IS_BUFFER (buffer));
|
||||
|
||||
meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
g_return_if_fail (meta);
|
||||
|
||||
cstream = gst_rtmp_chunk_streams_get (self->output_streams, meta->cstream);
|
||||
g_return_if_fail (cstream);
|
||||
|
||||
out_buffer = gst_rtmp_chunk_stream_serialize_start (cstream, buffer,
|
||||
self->out_chunk_size);
|
||||
g_return_if_fail (out_buffer);
|
||||
|
||||
out_ba = g_byte_array_new ();
|
||||
|
||||
while (out_buffer) {
|
||||
byte_array_take_buffer (out_ba, out_buffer);
|
||||
|
||||
out_buffer = gst_rtmp_chunk_stream_serialize_next (cstream,
|
||||
self->out_chunk_size);
|
||||
}
|
||||
|
||||
g_async_queue_push (self->output_queue, g_byte_array_free_to_bytes (out_ba));
|
||||
g_main_context_invoke (self->main_context, start_write, g_object_ref (self));
|
||||
}
|
||||
|
||||
guint
|
||||
gst_rtmp_connection_get_num_queued (GstRtmpConnection * connection)
|
||||
{
|
||||
return g_async_queue_length (connection->output_queue);
|
||||
}
|
||||
|
||||
guint
|
||||
gst_rtmp_connection_send_command (GstRtmpConnection * connection,
|
||||
GstRtmpCommandCallback response_command, gpointer user_data,
|
||||
guint32 stream_id, const gchar * command_name, const GstAmfNode * argument,
|
||||
...)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
gdouble transaction_id = 0;
|
||||
va_list ap;
|
||||
GBytes *payload;
|
||||
guint8 *data;
|
||||
gsize size;
|
||||
|
||||
if (connection->thread != g_thread_self ()) {
|
||||
GST_ERROR ("Called from wrong thread");
|
||||
}
|
||||
|
||||
GST_DEBUG ("Sending command '%s' on stream id %" G_GUINT32_FORMAT,
|
||||
command_name, stream_id);
|
||||
|
||||
if (response_command) {
|
||||
Transaction *t;
|
||||
|
||||
transaction_id = ++connection->transaction_count;
|
||||
|
||||
GST_LOG ("Registering %s for transid %.0f",
|
||||
GST_DEBUG_FUNCPTR_NAME (response_command), transaction_id);
|
||||
|
||||
t = transaction_new (transaction_id, response_command, user_data);
|
||||
|
||||
connection->transactions = g_list_append (connection->transactions, t);
|
||||
}
|
||||
|
||||
va_start (ap, argument);
|
||||
payload = gst_amf_serialize_command_valist (transaction_id,
|
||||
command_name, argument, ap);
|
||||
va_end (ap);
|
||||
|
||||
data = g_bytes_unref_to_data (payload, &size);
|
||||
buffer = gst_rtmp_message_new_wrapped (GST_RTMP_MESSAGE_TYPE_COMMAND_AMF0,
|
||||
3, stream_id, data, size);
|
||||
|
||||
gst_rtmp_connection_queue_message (connection, buffer);
|
||||
return transaction_id;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_expect_command (GstRtmpConnection * connection,
|
||||
GstRtmpCommandCallback response_command, gpointer user_data,
|
||||
guint32 stream_id, const gchar * command_name)
|
||||
{
|
||||
ExpectedCommand *ec;
|
||||
|
||||
g_return_if_fail (response_command);
|
||||
g_return_if_fail (command_name);
|
||||
g_return_if_fail (!is_command_response (command_name));
|
||||
|
||||
GST_LOG ("Registering %s for stream id %" G_GUINT32_FORMAT
|
||||
" name \"%s\"", GST_DEBUG_FUNCPTR_NAME (response_command),
|
||||
stream_id, command_name);
|
||||
|
||||
ec = expected_command_new (stream_id, command_name, response_command,
|
||||
user_data);
|
||||
|
||||
connection->expected_commands =
|
||||
g_list_append (connection->expected_commands, ec);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_send_ack (GstRtmpConnection * connection)
|
||||
{
|
||||
GstRtmpProtocolControl pc = {
|
||||
.type = GST_RTMP_MESSAGE_TYPE_ACKNOWLEDGEMENT,
|
||||
.param = (guint32) connection->total_input_bytes,
|
||||
};
|
||||
|
||||
gst_rtmp_connection_queue_message (connection,
|
||||
gst_rtmp_message_new_protocol_control (&pc));
|
||||
|
||||
connection->bytes_since_ack = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_rtmp_connection_send_ping_response (GstRtmpConnection * connection,
|
||||
guint32 event_data)
|
||||
{
|
||||
GstRtmpUserControl uc = {
|
||||
.type = GST_RTMP_USER_CONTROL_TYPE_PING_RESPONSE,
|
||||
.param = event_data,
|
||||
};
|
||||
|
||||
gst_rtmp_connection_queue_message (connection,
|
||||
gst_rtmp_message_new_user_control (&uc));
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_connection_request_window_size (GstRtmpConnection * connection,
|
||||
guint32 window_ack_size)
|
||||
{
|
||||
GstRtmpProtocolControl pc = {
|
||||
.type = GST_RTMP_MESSAGE_TYPE_WINDOW_ACK_SIZE,
|
||||
.param = window_ack_size,
|
||||
};
|
||||
|
||||
if (connection->out_window_ack_size == window_ack_size)
|
||||
return;
|
||||
|
||||
connection->out_window_ack_size = window_ack_size;
|
||||
|
||||
gst_rtmp_connection_queue_message (connection,
|
||||
gst_rtmp_message_new_protocol_control (&pc));
|
||||
}
|
82
gst/rtmp2/rtmp/rtmpconnection.h
Normal file
82
gst/rtmp2/rtmp/rtmpconnection.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/* GStreamer RTMP Library
|
||||
* Copyright (C) 2013 David Schleef <ds@schleef.org>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP_CONNECTION_H_
|
||||
#define _GST_RTMP_CONNECTION_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
#include "amf.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_RTMP_CONNECTION (gst_rtmp_connection_get_type())
|
||||
#define GST_RTMP_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTMP_CONNECTION,GstRtmpConnection))
|
||||
#define GST_IS_RTMP_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTMP_CONNECTION))
|
||||
|
||||
typedef struct _GstRtmpConnection GstRtmpConnection;
|
||||
|
||||
typedef void (*GstRtmpConnectionFunc)
|
||||
(GstRtmpConnection * connection, gpointer user_data);
|
||||
typedef void (*GstRtmpConnectionMessageFunc)
|
||||
(GstRtmpConnection * connection, GstBuffer * buffer, gpointer user_data);
|
||||
|
||||
typedef void (*GstRtmpCommandCallback) (const gchar * command_name,
|
||||
GPtrArray * arguments, gpointer user_data);
|
||||
|
||||
GType gst_rtmp_connection_get_type (void);
|
||||
|
||||
GstRtmpConnection *gst_rtmp_connection_new (GSocketConnection * connection);
|
||||
|
||||
GSocket *gst_rtmp_connection_get_socket (GstRtmpConnection * connection);
|
||||
|
||||
void gst_rtmp_connection_close (GstRtmpConnection * connection);
|
||||
void gst_rtmp_connection_close_and_unref (gpointer ptr);
|
||||
|
||||
void gst_rtmp_connection_set_input_handler (GstRtmpConnection * connection,
|
||||
GstRtmpConnectionMessageFunc callback, gpointer user_data,
|
||||
GDestroyNotify user_data_destroy);
|
||||
|
||||
void gst_rtmp_connection_set_output_handler (GstRtmpConnection * connection,
|
||||
GstRtmpConnectionFunc callback, gpointer user_data,
|
||||
GDestroyNotify user_data_destroy);
|
||||
|
||||
void gst_rtmp_connection_queue_bytes (GstRtmpConnection *self,
|
||||
GBytes * bytes);
|
||||
void gst_rtmp_connection_queue_message (GstRtmpConnection * connection,
|
||||
GstBuffer * buffer);
|
||||
guint gst_rtmp_connection_get_num_queued (GstRtmpConnection * connection);
|
||||
|
||||
guint gst_rtmp_connection_send_command (GstRtmpConnection * connection,
|
||||
GstRtmpCommandCallback response_command, gpointer user_data,
|
||||
guint32 stream_id, const gchar * command_name, const GstAmfNode * argument,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
|
||||
void gst_rtmp_connection_expect_command (GstRtmpConnection * connection,
|
||||
GstRtmpCommandCallback response_command, gpointer user_data,
|
||||
guint32 stream_id, const gchar * command_name);
|
||||
|
||||
void gst_rtmp_connection_request_window_size (GstRtmpConnection * connection,
|
||||
guint32 window_ack_size);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
311
gst/rtmp2/rtmp/rtmphandshake.c
Normal file
311
gst/rtmp2/rtmp/rtmphandshake.c
Normal file
|
@ -0,0 +1,311 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "rtmphandshake.h"
|
||||
#include "rtmputils.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <string.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_rtmp_handshake_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_rtmp_handshake_debug_category
|
||||
|
||||
static void
|
||||
init_debug (void)
|
||||
{
|
||||
static volatile gsize done = 0;
|
||||
if (g_once_init_enter (&done)) {
|
||||
GST_DEBUG_CATEGORY_INIT (gst_rtmp_handshake_debug_category, "rtmphandshake",
|
||||
0, "debug category for the rtmp connection handshake");
|
||||
g_once_init_leave (&done, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void client_handshake1_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
static void client_handshake2_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
static void client_handshake3_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
|
||||
static inline void
|
||||
serialize_u8 (GByteArray * array, guint8 value)
|
||||
{
|
||||
g_byte_array_append (array, (guint8 *) & value, sizeof value);
|
||||
}
|
||||
|
||||
static inline void
|
||||
serialize_u32 (GByteArray * array, guint32 value)
|
||||
{
|
||||
value = GUINT32_TO_BE (value);
|
||||
g_byte_array_append (array, (guint8 *) & value, sizeof value);
|
||||
}
|
||||
|
||||
#define SIZE_P0 1
|
||||
#define SIZE_P1 1536
|
||||
#define SIZE_P2 SIZE_P1
|
||||
#define SIZE_P0P1 (SIZE_P0 + SIZE_P1)
|
||||
#define SIZE_P0P1P2 (SIZE_P0P1 + SIZE_P2)
|
||||
#define SIZE_RANDOM (SIZE_P1 - 8)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GBytes *random_bytes;
|
||||
gboolean strict;
|
||||
} HandshakeData;
|
||||
|
||||
static GBytes *
|
||||
handshake_random_data (void)
|
||||
{
|
||||
G_STATIC_ASSERT (SIZE_RANDOM % 4 == 0);
|
||||
|
||||
GByteArray *ba = g_byte_array_sized_new (SIZE_RANDOM);
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < SIZE_RANDOM; i += 4) {
|
||||
serialize_u32 (ba, g_random_int ());
|
||||
}
|
||||
|
||||
return g_byte_array_free_to_bytes (ba);
|
||||
}
|
||||
|
||||
static HandshakeData *
|
||||
handshake_data_new (gboolean strict)
|
||||
{
|
||||
HandshakeData *data = g_slice_new0 (HandshakeData);
|
||||
data->random_bytes = handshake_random_data ();
|
||||
data->strict = strict;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
handshake_data_free (gpointer ptr)
|
||||
{
|
||||
HandshakeData *data = ptr;
|
||||
g_clear_pointer (&data->random_bytes, g_bytes_unref);
|
||||
g_slice_free (HandshakeData, data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handshake_data_check (HandshakeData * data, const guint8 * p2)
|
||||
{
|
||||
const guint8 *ourrandom = g_bytes_get_data (data->random_bytes, NULL);
|
||||
return memcmp (ourrandom, p2 + 8, SIZE_P2 - 8) == 0;
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
create_c0c1 (GBytes * random_bytes)
|
||||
{
|
||||
GByteArray *ba = g_byte_array_sized_new (SIZE_P0P1);
|
||||
|
||||
/* C0 version */
|
||||
serialize_u8 (ba, 3);
|
||||
|
||||
/* C1 time */
|
||||
serialize_u32 (ba, g_get_monotonic_time () / 1000);
|
||||
|
||||
/* C1 zero */
|
||||
serialize_u32 (ba, 0);
|
||||
|
||||
/* C1 random data */
|
||||
gst_rtmp_byte_array_append_bytes (ba, random_bytes);
|
||||
|
||||
GST_DEBUG ("Sending C0+C1");
|
||||
GST_MEMDUMP (">>> C0", ba->data, SIZE_P0);
|
||||
GST_MEMDUMP (">>> C1", ba->data + SIZE_P0, SIZE_P1);
|
||||
|
||||
return g_byte_array_free_to_bytes (ba);
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_client_handshake (GIOStream * stream, gboolean strict,
|
||||
GCancellable * cancellable, GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
HandshakeData *data;
|
||||
|
||||
g_return_if_fail (G_IS_IO_STREAM (stream));
|
||||
|
||||
init_debug ();
|
||||
GST_INFO ("Starting client handshake");
|
||||
|
||||
task = g_task_new (stream, cancellable, callback, user_data);
|
||||
data = handshake_data_new (strict);
|
||||
g_task_set_task_data (task, data, handshake_data_free);
|
||||
|
||||
{
|
||||
GOutputStream *os = g_io_stream_get_output_stream (stream);
|
||||
GBytes *bytes = create_c0c1 (data->random_bytes);
|
||||
|
||||
gst_rtmp_output_stream_write_all_bytes_async (os,
|
||||
bytes, G_PRIORITY_DEFAULT,
|
||||
g_task_get_cancellable (task), client_handshake1_done, task);
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
client_handshake1_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GOutputStream *os = G_OUTPUT_STREAM (source);
|
||||
GTask *task = user_data;
|
||||
GIOStream *stream = g_task_get_source_object (task);
|
||||
GInputStream *is = g_io_stream_get_input_stream (stream);
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
|
||||
res = gst_rtmp_output_stream_write_all_bytes_finish (os, result, &error);
|
||||
if (!res) {
|
||||
GST_ERROR ("Failed to send C0+C1: %s", error->message);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
GST_DEBUG ("Sent C0+C1, waiting for S0+S1+S2");
|
||||
gst_rtmp_input_stream_read_all_bytes_async (is, SIZE_P0P1P2,
|
||||
G_PRIORITY_DEFAULT, g_task_get_cancellable (task),
|
||||
client_handshake2_done, task);
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
create_c2 (const guint8 * s0s1s2)
|
||||
{
|
||||
G_STATIC_ASSERT (SIZE_P1 == SIZE_P2);
|
||||
|
||||
GByteArray *ba = g_byte_array_sized_new (SIZE_P2);
|
||||
gint64 c2time = g_get_monotonic_time ();
|
||||
|
||||
/* Copy S1 to C2 */
|
||||
g_byte_array_set_size (ba, SIZE_P2);
|
||||
memcpy (ba->data, s0s1s2 + SIZE_P0, SIZE_P1);
|
||||
|
||||
/* C2 time2 */
|
||||
GST_WRITE_UINT32_BE (ba->data + 4, c2time / 1000);
|
||||
|
||||
GST_DEBUG ("Sending C2");
|
||||
GST_MEMDUMP (">>> C2", ba->data, SIZE_P2);
|
||||
|
||||
return g_byte_array_free_to_bytes (ba);
|
||||
}
|
||||
|
||||
static void
|
||||
client_handshake2_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GInputStream *is = G_INPUT_STREAM (source);
|
||||
GTask *task = user_data;
|
||||
GIOStream *stream = g_task_get_source_object (task);
|
||||
HandshakeData *data = g_task_get_task_data (task);
|
||||
GError *error = NULL;
|
||||
GBytes *res;
|
||||
const guint8 *s0s1s2;
|
||||
gsize size;
|
||||
|
||||
res = gst_rtmp_input_stream_read_all_bytes_finish (is, result, &error);
|
||||
if (!res) {
|
||||
GST_ERROR ("Failed to read S0+S1+S2: %s", error->message);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
s0s1s2 = g_bytes_get_data (res, &size);
|
||||
if (size < SIZE_P0P1P2) {
|
||||
GST_ERROR ("Short read (want %d have %" G_GSIZE_FORMAT ")", SIZE_P0P1P2,
|
||||
size);
|
||||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
|
||||
"Short read (want %d have %" G_GSIZE_FORMAT ")", SIZE_P0P1P2, size);
|
||||
g_object_unref (task);
|
||||
goto out;
|
||||
}
|
||||
|
||||
GST_DEBUG ("Got S0+S1+S2");
|
||||
GST_MEMDUMP ("<<< S0", s0s1s2, SIZE_P0);
|
||||
GST_MEMDUMP ("<<< S1", s0s1s2 + SIZE_P0, SIZE_P1);
|
||||
GST_MEMDUMP ("<<< S2", s0s1s2 + SIZE_P0P1, SIZE_P2);
|
||||
|
||||
if (handshake_data_check (data, s0s1s2 + SIZE_P0P1)) {
|
||||
GST_DEBUG ("S2 random data matches C1");
|
||||
} else {
|
||||
if (data->strict) {
|
||||
GST_ERROR ("Handshake response data did not match");
|
||||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
"Handshake response data did not match");
|
||||
g_object_unref (task);
|
||||
goto out;
|
||||
}
|
||||
|
||||
GST_WARNING ("Handshake reponse data did not match; continuing anyway");
|
||||
}
|
||||
|
||||
{
|
||||
GOutputStream *os = g_io_stream_get_output_stream (stream);
|
||||
GBytes *bytes = create_c2 (s0s1s2);
|
||||
|
||||
gst_rtmp_output_stream_write_all_bytes_async (os,
|
||||
bytes, G_PRIORITY_DEFAULT,
|
||||
g_task_get_cancellable (task), client_handshake3_done, task);
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
out:
|
||||
g_bytes_unref (res);
|
||||
}
|
||||
|
||||
static void
|
||||
client_handshake3_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GOutputStream *os = G_OUTPUT_STREAM (source);
|
||||
GTask *task = user_data;
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
|
||||
res = gst_rtmp_output_stream_write_all_bytes_finish (os, result, &error);
|
||||
if (!res) {
|
||||
GST_ERROR ("Failed to send C2: %s", error->message);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
GST_DEBUG ("Sent C2");
|
||||
GST_INFO ("Client handshake finished");
|
||||
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_client_handshake_finish (GIOStream * stream, GAsyncResult * result,
|
||||
GError ** error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
35
gst/rtmp2/rtmp/rtmphandshake.h
Normal file
35
gst/rtmp2/rtmp/rtmphandshake.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP_HANDSHAKE_H_
|
||||
#define _GST_RTMP_HANDSHAKE_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void gst_rtmp_client_handshake (GIOStream * stream, gboolean strict,
|
||||
GCancellable * cancellable, GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gboolean gst_rtmp_client_handshake_finish (GIOStream * stream,
|
||||
GAsyncResult * result, GError ** error);
|
||||
|
||||
G_END_DECLS
|
||||
#endif
|
494
gst/rtmp2/rtmp/rtmpmessage.c
Normal file
494
gst/rtmp2/rtmp/rtmpmessage.c
Normal file
|
@ -0,0 +1,494 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "rtmpmessage.h"
|
||||
#include "rtmpchunkstream.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_rtmp_message_debug_category);
|
||||
#define GST_CAT_DEFAULT gst_rtmp_message_debug_category
|
||||
|
||||
gboolean
|
||||
gst_rtmp_message_type_is_valid (GstRtmpMessageType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GST_RTMP_MESSAGE_TYPE_INVALID:
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_CHUNK_SIZE:
|
||||
case GST_RTMP_MESSAGE_TYPE_ABORT_MESSAGE:
|
||||
case GST_RTMP_MESSAGE_TYPE_ACKNOWLEDGEMENT:
|
||||
case GST_RTMP_MESSAGE_TYPE_USER_CONTROL:
|
||||
case GST_RTMP_MESSAGE_TYPE_WINDOW_ACK_SIZE:
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_PEER_BANDWIDTH:
|
||||
case GST_RTMP_MESSAGE_TYPE_AUDIO:
|
||||
case GST_RTMP_MESSAGE_TYPE_VIDEO:
|
||||
case GST_RTMP_MESSAGE_TYPE_DATA_AMF3:
|
||||
case GST_RTMP_MESSAGE_TYPE_SHARED_OBJECT_AMF3:
|
||||
case GST_RTMP_MESSAGE_TYPE_COMMAND_AMF3:
|
||||
case GST_RTMP_MESSAGE_TYPE_DATA_AMF0:
|
||||
case GST_RTMP_MESSAGE_TYPE_SHARED_OBJECT_AMF0:
|
||||
case GST_RTMP_MESSAGE_TYPE_COMMAND_AMF0:
|
||||
case GST_RTMP_MESSAGE_TYPE_AGGREGATE:
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_message_type_is_protocol_control (GstRtmpMessageType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_CHUNK_SIZE:
|
||||
case GST_RTMP_MESSAGE_TYPE_ABORT_MESSAGE:
|
||||
case GST_RTMP_MESSAGE_TYPE_ACKNOWLEDGEMENT:
|
||||
case GST_RTMP_MESSAGE_TYPE_WINDOW_ACK_SIZE:
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_PEER_BANDWIDTH:
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gst_rtmp_message_type_get_nick (GstRtmpMessageType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GST_RTMP_MESSAGE_TYPE_INVALID:
|
||||
return "invalid";
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_CHUNK_SIZE:
|
||||
return "set-chunk-size";
|
||||
case GST_RTMP_MESSAGE_TYPE_ABORT_MESSAGE:
|
||||
return "abort-message";
|
||||
case GST_RTMP_MESSAGE_TYPE_ACKNOWLEDGEMENT:
|
||||
return "acknowledgement";
|
||||
case GST_RTMP_MESSAGE_TYPE_USER_CONTROL:
|
||||
return "user-control";
|
||||
case GST_RTMP_MESSAGE_TYPE_WINDOW_ACK_SIZE:
|
||||
return "window-ack-size";
|
||||
case GST_RTMP_MESSAGE_TYPE_SET_PEER_BANDWIDTH:
|
||||
return "set-peer-bandwidth";
|
||||
case GST_RTMP_MESSAGE_TYPE_AUDIO:
|
||||
return "audio";
|
||||
case GST_RTMP_MESSAGE_TYPE_VIDEO:
|
||||
return "video";
|
||||
case GST_RTMP_MESSAGE_TYPE_DATA_AMF3:
|
||||
return "data-amf3";
|
||||
case GST_RTMP_MESSAGE_TYPE_SHARED_OBJECT_AMF3:
|
||||
return "shared-object-amf3";
|
||||
case GST_RTMP_MESSAGE_TYPE_COMMAND_AMF3:
|
||||
return "command-amf3";
|
||||
case GST_RTMP_MESSAGE_TYPE_DATA_AMF0:
|
||||
return "data-amf0";
|
||||
case GST_RTMP_MESSAGE_TYPE_SHARED_OBJECT_AMF0:
|
||||
return "shared-object-amf0";
|
||||
case GST_RTMP_MESSAGE_TYPE_COMMAND_AMF0:
|
||||
return "command-amf0";
|
||||
case GST_RTMP_MESSAGE_TYPE_AGGREGATE:
|
||||
return "aggregate";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gst_rtmp_user_control_type_get_nick (GstRtmpUserControlType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_BEGIN:
|
||||
return "stream-begin";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_EOF:
|
||||
return "stream-eof";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_DRY:
|
||||
return "stream-dry";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_SET_BUFFER_LENGTH:
|
||||
return "set-buffer-length";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_STREAM_IS_RECORDED:
|
||||
return "stream-is-recorded";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_PING_REQUEST:
|
||||
return "ping-request";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_PING_RESPONSE:
|
||||
return "ping-response";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_SWF_VERIFICATION_REQUEST:
|
||||
return "swf-verification-request";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_SWF_VERIFICATION_RESPONSE:
|
||||
return "swf-verification-response";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_BUFFER_EMPTY:
|
||||
return "buffer-empty";
|
||||
case GST_RTMP_USER_CONTROL_TYPE_BUFFER_READY:
|
||||
return "buffer-ready";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
GType
|
||||
gst_rtmp_meta_api_get_type (void)
|
||||
{
|
||||
static volatile GType type = 0;
|
||||
static const gchar *tags[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
if (g_once_init_enter (&type)) {
|
||||
GType _type = gst_meta_api_type_register ("GstRtmpMetaAPI", tags);
|
||||
GST_DEBUG_CATEGORY_INIT (gst_rtmp_message_debug_category,
|
||||
"rtmpmessage", 0, "debug category for rtmp messages");
|
||||
g_once_init_leave (&type, _type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *emeta = (GstRtmpMeta *) meta;
|
||||
|
||||
emeta->cstream = 0;
|
||||
emeta->ts_delta = 0;
|
||||
emeta->size = 0;
|
||||
emeta->type = GST_RTMP_MESSAGE_TYPE_INVALID;
|
||||
emeta->mstream = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_rtmp_meta_transform (GstBuffer * dest, GstMeta * meta, GstBuffer * buffer,
|
||||
GQuark type, gpointer data)
|
||||
{
|
||||
GstRtmpMeta *smeta, *dmeta;
|
||||
|
||||
if (!GST_META_TRANSFORM_IS_COPY (type)) {
|
||||
/* We only support copy transforms */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
smeta = (GstRtmpMeta *) meta;
|
||||
dmeta = gst_buffer_get_rtmp_meta (dest);
|
||||
if (!dmeta) {
|
||||
dmeta = gst_buffer_add_rtmp_meta (dest);
|
||||
}
|
||||
|
||||
dmeta->cstream = smeta->cstream;
|
||||
dmeta->ts_delta = smeta->ts_delta;
|
||||
dmeta->size = smeta->size;
|
||||
dmeta->type = smeta->type;
|
||||
dmeta->mstream = smeta->mstream;
|
||||
|
||||
return dmeta != NULL;
|
||||
}
|
||||
|
||||
const GstMetaInfo *
|
||||
gst_rtmp_meta_get_info (void)
|
||||
{
|
||||
static const GstMetaInfo *rtmp_meta_info = NULL;
|
||||
|
||||
if (g_once_init_enter (&rtmp_meta_info)) {
|
||||
const GstMetaInfo *meta = gst_meta_register (GST_RTMP_META_API_TYPE,
|
||||
"GstRtmpMeta", sizeof *meta, gst_rtmp_meta_init, NULL,
|
||||
gst_rtmp_meta_transform);
|
||||
g_once_init_leave (&rtmp_meta_info, meta);
|
||||
}
|
||||
return rtmp_meta_info;
|
||||
}
|
||||
|
||||
GstRtmpMeta *
|
||||
gst_buffer_add_rtmp_meta (GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta;
|
||||
|
||||
g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
|
||||
|
||||
meta = (GstRtmpMeta *) gst_buffer_add_meta (buffer, GST_RTMP_META_INFO, NULL);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_message_new (GstRtmpMessageType type, guint32 cstream, guint32 mstream)
|
||||
{
|
||||
GstBuffer *buffer = gst_buffer_new ();
|
||||
GstRtmpMeta *meta = gst_buffer_add_rtmp_meta (buffer);
|
||||
|
||||
meta->type = type;
|
||||
meta->cstream = cstream;
|
||||
meta->mstream = mstream;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_message_new_wrapped (GstRtmpMessageType type, guint32 cstream,
|
||||
guint32 mstream, guint8 * data, gsize size)
|
||||
{
|
||||
GstBuffer *message = gst_rtmp_message_new (type, cstream, mstream);
|
||||
|
||||
gst_buffer_append_memory (message,
|
||||
gst_memory_new_wrapped (0, data, size, 0, size, data, g_free));
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_buffer_dump (GstBuffer * buffer, const gchar * prefix)
|
||||
{
|
||||
GstRtmpMeta *meta;
|
||||
GstMapInfo map;
|
||||
|
||||
if (G_LIKELY (GST_LEVEL_LOG > _gst_debug_min || GST_LEVEL_LOG >
|
||||
gst_debug_category_get_threshold (GST_CAT_DEFAULT))) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_return_if_fail (GST_IS_BUFFER (buffer));
|
||||
g_return_if_fail (prefix);
|
||||
|
||||
GST_LOG ("%s %" GST_PTR_FORMAT, prefix, buffer);
|
||||
|
||||
meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
if (meta) {
|
||||
GST_LOG ("%s cstream:%-4" G_GUINT32_FORMAT " mstream:%-4" G_GUINT32_FORMAT
|
||||
" ts:%-8" G_GUINT32_FORMAT " len:%-6" G_GUINT32_FORMAT " type:%s",
|
||||
prefix, meta->cstream, meta->mstream, meta->ts_delta, meta->size,
|
||||
gst_rtmp_message_type_get_nick (meta->type));
|
||||
}
|
||||
|
||||
if (G_LIKELY (GST_LEVEL_MEMDUMP > _gst_debug_min || GST_LEVEL_MEMDUMP >
|
||||
gst_debug_category_get_threshold (GST_CAT_DEFAULT))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) {
|
||||
GST_ERROR ("Failed to map %" GST_PTR_FORMAT " for memdump", buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.size > 0) {
|
||||
GST_MEMDUMP (prefix, map.data, map.size);
|
||||
}
|
||||
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
}
|
||||
|
||||
GstRtmpMessageType
|
||||
gst_rtmp_message_get_type (GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
g_return_val_if_fail (meta, GST_RTMP_MESSAGE_TYPE_INVALID);
|
||||
return meta->type;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_message_is_protocol_control (GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
|
||||
g_return_val_if_fail (meta, FALSE);
|
||||
|
||||
if (!gst_rtmp_message_type_is_protocol_control (meta->type)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (meta->cstream != GST_RTMP_CHUNK_STREAM_PROTOCOL) {
|
||||
GST_WARNING ("Protocol control message on chunk stream %"
|
||||
G_GUINT32_FORMAT ", not 2", meta->cstream);
|
||||
}
|
||||
|
||||
if (meta->mstream != 0) {
|
||||
GST_WARNING ("Protocol control message on message stream %"
|
||||
G_GUINT32_FORMAT ", not 0", meta->mstream);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_message_is_user_control (GstBuffer * buffer)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
|
||||
g_return_val_if_fail (meta, FALSE);
|
||||
|
||||
if (meta->type != GST_RTMP_MESSAGE_TYPE_USER_CONTROL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (meta->cstream != GST_RTMP_CHUNK_STREAM_PROTOCOL) {
|
||||
GST_WARNING ("User control message on chunk stream %"
|
||||
G_GUINT32_FORMAT ", not 2", meta->cstream);
|
||||
}
|
||||
|
||||
if (meta->mstream != 0) {
|
||||
GST_WARNING ("User control message on message stream %"
|
||||
G_GUINT32_FORMAT ", not 0", meta->mstream);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
pc_has_param2 (GstRtmpMessageType type)
|
||||
{
|
||||
return type == GST_RTMP_MESSAGE_TYPE_SET_PEER_BANDWIDTH;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_message_parse_protocol_control (GstBuffer * buffer,
|
||||
GstRtmpProtocolControl * out)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
GstMapInfo map;
|
||||
GstRtmpProtocolControl pc;
|
||||
gsize pc_size = 4;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
g_return_val_if_fail (meta, FALSE);
|
||||
g_return_val_if_fail (gst_rtmp_message_type_is_protocol_control (meta->type),
|
||||
FALSE);
|
||||
|
||||
if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) {
|
||||
GST_ERROR ("can't map protocol control message");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pc.type = meta->type;
|
||||
pc_size = pc_has_param2 (pc.type) ? 5 : 4;
|
||||
|
||||
if (map.size < pc_size) {
|
||||
GST_ERROR ("can't read protocol control param");
|
||||
goto err;
|
||||
} else if (map.size > pc_size) {
|
||||
GST_WARNING ("overlength protocol control: %" G_GSIZE_FORMAT " > %"
|
||||
G_GSIZE_FORMAT, map.size, pc_size);
|
||||
}
|
||||
|
||||
pc.param = GST_READ_UINT32_BE (map.data);
|
||||
pc.param2 = pc_has_param2 (pc.type) ? GST_READ_UINT8 (map.data + 4) : 0;
|
||||
|
||||
ret = TRUE;
|
||||
if (out) {
|
||||
*out = pc;
|
||||
}
|
||||
|
||||
err:
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_message_new_protocol_control (GstRtmpProtocolControl * pc)
|
||||
{
|
||||
guint8 *data;
|
||||
gsize size;
|
||||
|
||||
g_return_val_if_fail (pc, NULL);
|
||||
g_return_val_if_fail (gst_rtmp_message_type_is_protocol_control (pc->type),
|
||||
NULL);
|
||||
|
||||
size = pc_has_param2 (pc->type) ? 5 : 4;
|
||||
|
||||
data = g_malloc (size);
|
||||
GST_WRITE_UINT32_BE (data, pc->param);
|
||||
if (pc_has_param2 (pc->type)) {
|
||||
GST_WRITE_UINT32_BE (data + 4, pc->param2);
|
||||
}
|
||||
|
||||
return gst_rtmp_message_new_wrapped (pc->type,
|
||||
GST_RTMP_CHUNK_STREAM_PROTOCOL, 0, data, size);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
uc_has_param2 (GstRtmpUserControlType type)
|
||||
{
|
||||
return type == GST_RTMP_USER_CONTROL_TYPE_SET_BUFFER_LENGTH;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_message_parse_user_control (GstBuffer * buffer,
|
||||
GstRtmpUserControl * out)
|
||||
{
|
||||
GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (buffer);
|
||||
GstMapInfo map;
|
||||
GstRtmpUserControl uc;
|
||||
gsize uc_size;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
g_return_val_if_fail (meta, FALSE);
|
||||
g_return_val_if_fail (meta->type == GST_RTMP_MESSAGE_TYPE_USER_CONTROL,
|
||||
FALSE);
|
||||
|
||||
if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) {
|
||||
GST_ERROR ("can't map user control message");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (map.size < 2) {
|
||||
GST_ERROR ("can't read user control type");
|
||||
goto err;
|
||||
}
|
||||
|
||||
uc.type = GST_READ_UINT16_BE (map.data);
|
||||
uc_size = uc_has_param2 (uc.type) ? 10 : 6;
|
||||
|
||||
if (map.size < uc_size) {
|
||||
GST_ERROR ("can't read user control param");
|
||||
goto err;
|
||||
} else if (map.size > uc_size) {
|
||||
GST_WARNING ("overlength user control: %" G_GSIZE_FORMAT " > %"
|
||||
G_GSIZE_FORMAT, map.size, uc_size);
|
||||
}
|
||||
|
||||
uc.param = GST_READ_UINT32_BE (map.data + 2);
|
||||
uc.param2 = uc_has_param2 (uc.type) ? GST_READ_UINT32_BE (map.data + 6) : 0;
|
||||
|
||||
ret = TRUE;
|
||||
if (out) {
|
||||
*out = uc;
|
||||
}
|
||||
|
||||
err:
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
gst_rtmp_message_new_user_control (GstRtmpUserControl * uc)
|
||||
{
|
||||
guint8 *data;
|
||||
gsize size;
|
||||
|
||||
g_return_val_if_fail (uc, NULL);
|
||||
|
||||
size = uc_has_param2 (uc->type) ? 10 : 6;
|
||||
|
||||
data = g_malloc (size);
|
||||
GST_WRITE_UINT16_BE (data, uc->type);
|
||||
GST_WRITE_UINT32_BE (data + 2, uc->param);
|
||||
if (uc_has_param2 (uc->type)) {
|
||||
GST_WRITE_UINT32_BE (data + 6, uc->param2);
|
||||
}
|
||||
|
||||
return gst_rtmp_message_new_wrapped (GST_RTMP_MESSAGE_TYPE_USER_CONTROL,
|
||||
GST_RTMP_CHUNK_STREAM_PROTOCOL, 0, data, size);
|
||||
}
|
142
gst/rtmp2/rtmp/rtmpmessage.h
Normal file
142
gst/rtmp2/rtmp/rtmpmessage.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/* GStreamer RTMP Library
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _GST_RTMP_MESSAGE_H_
|
||||
#define _GST_RTMP_MESSAGE_H_
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
GST_RTMP_MESSAGE_TYPE_INVALID = 0,
|
||||
GST_RTMP_MESSAGE_TYPE_SET_CHUNK_SIZE = 1,
|
||||
GST_RTMP_MESSAGE_TYPE_ABORT_MESSAGE = 2,
|
||||
GST_RTMP_MESSAGE_TYPE_ACKNOWLEDGEMENT = 3,
|
||||
GST_RTMP_MESSAGE_TYPE_USER_CONTROL = 4,
|
||||
GST_RTMP_MESSAGE_TYPE_WINDOW_ACK_SIZE = 5,
|
||||
GST_RTMP_MESSAGE_TYPE_SET_PEER_BANDWIDTH = 6,
|
||||
GST_RTMP_MESSAGE_TYPE_AUDIO = 8,
|
||||
GST_RTMP_MESSAGE_TYPE_VIDEO = 9,
|
||||
GST_RTMP_MESSAGE_TYPE_DATA_AMF3 = 15,
|
||||
GST_RTMP_MESSAGE_TYPE_SHARED_OBJECT_AMF3 = 16,
|
||||
GST_RTMP_MESSAGE_TYPE_COMMAND_AMF3 = 17,
|
||||
GST_RTMP_MESSAGE_TYPE_DATA_AMF0 = 18,
|
||||
GST_RTMP_MESSAGE_TYPE_SHARED_OBJECT_AMF0 = 19,
|
||||
GST_RTMP_MESSAGE_TYPE_COMMAND_AMF0 = 20,
|
||||
GST_RTMP_MESSAGE_TYPE_AGGREGATE = 22,
|
||||
} GstRtmpMessageType;
|
||||
|
||||
gboolean gst_rtmp_message_type_is_valid (GstRtmpMessageType type);
|
||||
gboolean gst_rtmp_message_type_is_protocol_control (GstRtmpMessageType type);
|
||||
const gchar * gst_rtmp_message_type_get_nick (GstRtmpMessageType type);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_RTMP_USER_CONTROL_TYPE_STREAM_BEGIN = 0,
|
||||
GST_RTMP_USER_CONTROL_TYPE_STREAM_EOF = 1,
|
||||
GST_RTMP_USER_CONTROL_TYPE_STREAM_DRY = 2,
|
||||
GST_RTMP_USER_CONTROL_TYPE_SET_BUFFER_LENGTH = 3,
|
||||
GST_RTMP_USER_CONTROL_TYPE_STREAM_IS_RECORDED = 4,
|
||||
GST_RTMP_USER_CONTROL_TYPE_PING_REQUEST = 6,
|
||||
GST_RTMP_USER_CONTROL_TYPE_PING_RESPONSE = 7,
|
||||
|
||||
/* undocumented */
|
||||
GST_RTMP_USER_CONTROL_TYPE_SWF_VERIFICATION_REQUEST = 26,
|
||||
GST_RTMP_USER_CONTROL_TYPE_SWF_VERIFICATION_RESPONSE = 27,
|
||||
GST_RTMP_USER_CONTROL_TYPE_BUFFER_EMPTY = 31,
|
||||
GST_RTMP_USER_CONTROL_TYPE_BUFFER_READY = 32,
|
||||
} GstRtmpUserControlType;
|
||||
|
||||
const gchar * gst_rtmp_user_control_type_get_nick (
|
||||
GstRtmpUserControlType type);
|
||||
|
||||
#define GST_RTMP_META_API_TYPE (gst_rtmp_meta_api_get_type())
|
||||
#define GST_RTMP_META_INFO (gst_rtmp_meta_get_info())
|
||||
typedef struct _GstRtmpMeta GstRtmpMeta;
|
||||
|
||||
struct _GstRtmpMeta {
|
||||
GstMeta meta;
|
||||
guint32 cstream;
|
||||
guint32 ts_delta;
|
||||
guint32 size;
|
||||
GstRtmpMessageType type;
|
||||
guint32 mstream;
|
||||
};
|
||||
|
||||
GType gst_rtmp_meta_api_get_type (void);
|
||||
const GstMetaInfo * gst_rtmp_meta_get_info (void);
|
||||
|
||||
GstRtmpMeta * gst_buffer_add_rtmp_meta (GstBuffer * buffer);
|
||||
|
||||
static inline GstRtmpMeta *
|
||||
gst_buffer_get_rtmp_meta (GstBuffer * buffer)
|
||||
{
|
||||
return (GstRtmpMeta *) gst_buffer_get_meta (buffer, GST_RTMP_META_API_TYPE);
|
||||
}
|
||||
|
||||
GstBuffer * gst_rtmp_message_new (GstRtmpMessageType type, guint32 cstream,
|
||||
guint32 mstream);
|
||||
GstBuffer * gst_rtmp_message_new_wrapped (GstRtmpMessageType type, guint32 cstream,
|
||||
guint32 mstream, guint8 * data, gsize size);
|
||||
|
||||
void gst_rtmp_buffer_dump (GstBuffer * buffer, const gchar * prefix);
|
||||
|
||||
GstRtmpMessageType gst_rtmp_message_get_type (GstBuffer * buffer);
|
||||
gboolean gst_rtmp_message_is_protocol_control (GstBuffer * buffer);
|
||||
gboolean gst_rtmp_message_is_user_control (GstBuffer * buffer);
|
||||
|
||||
typedef struct {
|
||||
GstRtmpMessageType type;
|
||||
|
||||
/* for SET_CHUNK_SIZE: chunk size */
|
||||
/* for ABORT_MESSAGE: chunk stream ID */
|
||||
/* for ACKNOWLEDGEMENT: acknowledged byte count */
|
||||
/* for WINDOW_ACK_SIZE and SET_PEER_BANDWIDTH: acknowledgement window size */
|
||||
guint32 param;
|
||||
|
||||
/* for SET_PEER_BANDWIDTH: limit type */
|
||||
guint8 param2;
|
||||
} GstRtmpProtocolControl;
|
||||
|
||||
gboolean gst_rtmp_message_parse_protocol_control (GstBuffer * buffer,
|
||||
GstRtmpProtocolControl * out);
|
||||
|
||||
GstBuffer * gst_rtmp_message_new_protocol_control (GstRtmpProtocolControl * pc);
|
||||
|
||||
typedef struct {
|
||||
GstRtmpUserControlType type;
|
||||
|
||||
/* for STREAM_BEGIN to STREAM_IS_RECORDED: message stream ID */
|
||||
/* for PING_REQUEST and PING_RESPONSE: timestamp of request */
|
||||
guint32 param;
|
||||
|
||||
/* for SET_BUFFER_LENGTH: buffer length in ms */
|
||||
guint32 param2;
|
||||
} GstRtmpUserControl;
|
||||
|
||||
gboolean gst_rtmp_message_parse_user_control (GstBuffer * buffer,
|
||||
GstRtmpUserControl * out);
|
||||
|
||||
GstBuffer * gst_rtmp_message_new_user_control (GstRtmpUserControl * uc);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
246
gst/rtmp2/rtmp/rtmputils.c
Normal file
246
gst/rtmp2/rtmp/rtmputils.c
Normal file
|
@ -0,0 +1,246 @@
|
|||
/* GStreamer RTMP Library
|
||||
* Copyright (C) 2013 David Schleef <ds@schleef.org>
|
||||
* 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 Street, Suite 500,
|
||||
* Boston, MA 02110-1335, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "rtmputils.h"
|
||||
#include <string.h>
|
||||
|
||||
static void read_all_bytes_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
static void write_all_bytes_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data);
|
||||
|
||||
void
|
||||
gst_rtmp_byte_array_append_bytes (GByteArray * bytearray, GBytes * bytes)
|
||||
{
|
||||
const guint8 *data;
|
||||
gsize size;
|
||||
guint offset;
|
||||
|
||||
g_return_if_fail (bytearray);
|
||||
|
||||
offset = bytearray->len;
|
||||
data = g_bytes_get_data (bytes, &size);
|
||||
|
||||
g_return_if_fail (data);
|
||||
|
||||
g_byte_array_set_size (bytearray, offset + size);
|
||||
memcpy (bytearray->data + offset, data, size);
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_input_stream_read_all_bytes_async (GInputStream * stream, gsize count,
|
||||
int io_priority, GCancellable * cancellable, GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
GByteArray *ba;
|
||||
|
||||
g_return_if_fail (G_IS_INPUT_STREAM (stream));
|
||||
|
||||
task = g_task_new (stream, cancellable, callback, user_data);
|
||||
|
||||
ba = g_byte_array_sized_new (count);
|
||||
g_byte_array_set_size (ba, count);
|
||||
g_task_set_task_data (task, ba, (GDestroyNotify) g_byte_array_unref);
|
||||
|
||||
g_input_stream_read_all_async (stream, ba->data, count, io_priority,
|
||||
cancellable, read_all_bytes_done, task);
|
||||
}
|
||||
|
||||
static void
|
||||
read_all_bytes_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GInputStream *is = G_INPUT_STREAM (source);
|
||||
GTask *task = user_data;
|
||||
GByteArray *ba = g_task_get_task_data (task);
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
gsize bytes_read;
|
||||
GBytes *bytes;
|
||||
|
||||
res = g_input_stream_read_all_finish (is, result, &bytes_read, &error);
|
||||
if (!res) {
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_byte_array_set_size (ba, bytes_read);
|
||||
bytes = g_byte_array_free_to_bytes (g_byte_array_ref (ba));
|
||||
|
||||
g_task_return_pointer (task, bytes, (GDestroyNotify) g_bytes_unref);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
GBytes *
|
||||
gst_rtmp_input_stream_read_all_bytes_finish (GInputStream * stream,
|
||||
GAsyncResult * result, GError ** error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
void
|
||||
gst_rtmp_output_stream_write_all_bytes_async (GOutputStream * stream,
|
||||
GBytes * bytes, int io_priority, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
const void *data;
|
||||
gsize size;
|
||||
|
||||
g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
|
||||
g_return_if_fail (bytes);
|
||||
|
||||
data = g_bytes_get_data (bytes, &size);
|
||||
g_return_if_fail (data);
|
||||
|
||||
task = g_task_new (stream, cancellable, callback, user_data);
|
||||
g_task_set_task_data (task, g_bytes_ref (bytes),
|
||||
(GDestroyNotify) g_bytes_unref);
|
||||
|
||||
g_output_stream_write_all_async (stream, data, size, io_priority,
|
||||
cancellable, write_all_bytes_done, task);
|
||||
}
|
||||
|
||||
static void
|
||||
write_all_bytes_done (GObject * source, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GOutputStream *os = G_OUTPUT_STREAM (source);
|
||||
GTask *task = user_data;
|
||||
GError *error = NULL;
|
||||
gboolean res;
|
||||
|
||||
res = g_output_stream_write_all_finish (os, result, NULL, &error);
|
||||
if (!res) {
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_rtmp_output_stream_write_all_bytes_finish (GOutputStream * stream,
|
||||
GAsyncResult * result, GError ** error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static const gchar ascii_table[128] = {
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
' ', '!', 0x0, '#', '$', '%', '&', '\'',
|
||||
'(', ')', '*', '+', ',', '-', '.', '/',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', ':', ';', '<', '=', '>', '?',
|
||||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
||||
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
|
||||
'X', 'Y', 'Z', '[', 0x0, ']', '^', '_',
|
||||
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
|
||||
'x', 'y', 'z', '{', '|', '}', '~', 0x0,
|
||||
};
|
||||
|
||||
static const gchar ascii_escapes[128] = {
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 'a',
|
||||
'b', 't', 'n', 'v', 'f', 'r', 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, '"', 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, '\\', 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
void
|
||||
gst_rtmp_string_print_escaped (GString * string, const gchar * data,
|
||||
gssize size)
|
||||
{
|
||||
gssize i;
|
||||
|
||||
g_return_if_fail (string);
|
||||
|
||||
if (!data) {
|
||||
g_string_append (string, "(NULL)");
|
||||
return;
|
||||
}
|
||||
|
||||
g_string_append_c (string, '"');
|
||||
|
||||
for (i = 0; size < 0 ? data[i] != 0 : i < size; i++) {
|
||||
guchar c = data[i];
|
||||
|
||||
if (G_LIKELY (c < G_N_ELEMENTS (ascii_table))) {
|
||||
if (ascii_table[c]) {
|
||||
g_string_append_c (string, c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ascii_escapes[c]) {
|
||||
g_string_append_c (string, '\\');
|
||||
g_string_append_c (string, ascii_escapes[c]);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
gunichar uc = g_utf8_get_char_validated (data + i,
|
||||
size < 0 ? -1 : size - i);
|
||||
if (uc != (gunichar) (-2) && uc != (gunichar) (-1)) {
|
||||
if (g_unichar_isprint (uc)) {
|
||||
g_string_append_unichar (string, uc);
|
||||
} else if (uc <= G_MAXUINT16) {
|
||||
g_string_append_printf (string, "\\u%04X", uc);
|
||||
} else {
|
||||
g_string_append_printf (string, "\\U%08X", uc);
|
||||
}
|
||||
|
||||
i += g_utf8_skip[c] - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
g_string_append_printf (string, "\\x%02X", c);
|
||||
}
|
||||
|
||||
g_string_append_c (string, '"');
|
||||
|
||||
}
|
46
gst/rtmp2/rtmp/rtmputils.h
Normal file
46
gst/rtmp2/rtmp/rtmputils.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/* GStreamer RTMP Library
|
||||
* Copyright (C) 2013 David Schleef <ds@schleef.org>
|
||||
*
|
||||
* 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_RTMP_UTILS_H_
|
||||
#define _GST_RTMP_UTILS_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void gst_rtmp_byte_array_append_bytes (GByteArray * bytearray, GBytes * bytes);
|
||||
|
||||
void gst_rtmp_input_stream_read_all_bytes_async (GInputStream * stream,
|
||||
gsize count, int io_priority, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data);
|
||||
GBytes * gst_rtmp_input_stream_read_all_bytes_finish (GInputStream * stream,
|
||||
GAsyncResult * result, GError ** error);
|
||||
|
||||
void gst_rtmp_output_stream_write_all_bytes_async (GOutputStream * stream,
|
||||
GBytes * bytes, int io_priority, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data);
|
||||
gboolean gst_rtmp_output_stream_write_all_bytes_finish (GOutputStream * stream,
|
||||
GAsyncResult * result, GError ** error);
|
||||
|
||||
void gst_rtmp_string_print_escaped (GString * string, const gchar *data,
|
||||
gssize size);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
|
@ -49,6 +49,7 @@ option('proxy', type : 'feature', value : 'auto')
|
|||
option('rawparse', type : 'feature', value : 'auto')
|
||||
option('removesilence', type : 'feature', value : 'auto')
|
||||
option('rist', type : 'feature', value : 'auto')
|
||||
option('rtmp2', type : 'feature', value : 'auto')
|
||||
option('rtp', type : 'feature', value : 'auto')
|
||||
option('sdp', type : 'feature', value : 'auto')
|
||||
option('segmentclip', type : 'feature', value : 'auto')
|
||||
|
|
Loading…
Reference in a new issue