Add files from gst-rtmp

For master, without autotools.
This commit is contained in:
Vivia Nikolaidou 2019-11-05 13:52:55 +00:00 committed by GStreamer Merge Bot
parent 5320bb9085
commit 2386858a91
25 changed files with 8143 additions and 4 deletions

View file

@ -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
View 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
View 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)

View 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;
}

View 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

File diff suppressed because it is too large Load diff

34
gst/rtmp2/gstrtmp2sink.h Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

112
gst/rtmp2/rtmp/amf.h Normal file
View 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

View 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;
}

View 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

File diff suppressed because it is too large Load diff

101
gst/rtmp2/rtmp/rtmpclient.h Normal file
View 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

View 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));
}

View 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

View 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);
}

View 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

View 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);
}

View 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
View 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, '"');
}

View 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

View file

@ -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')