[svn-commits] phsultan: branch phsultan/rtmp-support r205765 - in /team/phsultan/rtmp-suppo...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Fri Jul 10 04:44:59 CDT 2009


Author: phsultan
Date: Fri Jul 10 04:44:55 2009
New Revision: 205765

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=205765
Log:
Added the code for the RTMP driver, a documentation file and sample configuration file

Added:
    team/phsultan/rtmp-support/channels/chan_rtmp.c   (with props)
    team/phsultan/rtmp-support/configs/rtmp.conf.sample   (with props)
    team/phsultan/rtmp-support/doc/rtmp.txt   (with props)
    team/phsultan/rtmp-support/include/asterisk/rtmp.h   (with props)

Added: team/phsultan/rtmp-support/channels/chan_rtmp.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/phsultan/rtmp-support/channels/chan_rtmp.c?view=auto&rev=205765
==============================================================================
--- team/phsultan/rtmp-support/channels/chan_rtmp.c (added)
+++ team/phsultan/rtmp-support/channels/chan_rtmp.c Fri Jul 10 04:44:55 2009
@@ -1,0 +1,3051 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief  RTMP (Adobe's Flash Media Server and Red5 server) support
+ * 
+ * \author Philippe Sultan <philippe.sultan at inria.fr>
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+	<depend>avcodec</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <libavcodec/avcodec.h>
+
+#define REF_DEBUG 1
+#include "asterisk/astobj2.h"
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/strings.h"
+#include "asterisk/app.h"
+
+#include "asterisk/rtmp.h"
+
+
+struct rtmp_client {
+	enum rtmp_state state;
+	int fd;
+	pthread_t thread;
+} *client = NULL;
+
+static struct sockaddr_in rtmpserver;
+static char rtmpserverstr[50];
+static int port = 1935;
+static char application[50];
+
+static const char tdesc[] = "RTMP driver";
+static const char config[] = "rtmp.conf";
+
+static int prefformat = AST_FORMAT_SLINEAR;
+
+static char context[AST_MAX_EXTENSION] = "default";
+static char type[] = "RTMP";
+
+/**
+ * This structure stores information about an RTMP connection :
+ * - the number of streams (FLEX NetStream objects) that form the Asterisk
+ *   channel. A minimum of 2 streams are used per Asterisk channel to 
+ *   receive/send data from/to the RTMP server.
+ * - the name of the stream that Asterisk will publish to the RTMP server
+ * - the name of the stream that Asterisk will retrieve from the RTMP server.
+ *   If numstreams is higher than 2, Asterisk will ask the RTMP server to play
+ *   streams named 'readstream-0', 'readstream-1', etc.
+ */
+struct rtmp_pvt {
+	struct ast_channel *owner;
+	AVCodec *encoder;
+	AVCodec *decoder;
+	AVCodecContext *encoding_context;
+	AVCodecContext *decoding_context;
+	ReSampleContext *tortmp_resample_context;
+	ReSampleContext *fromrtmp_resample_context;
+	unsigned int rtmpinputrate;			/* default : 11000 Hz */
+	unsigned int astinputrate;			/* default : 8000 Hz */
+
+	/* Each stream is a member of a group of at least 2 streams :
+	 * - the first stream carries data to be published to the RTMP server
+	 * - each subsequent stream handles data (audio/video) coming
+	 *   from the RTMP server
+	 *
+	 * numstreams contains the number of streams contained in the group
+	 */
+	uint32_t streamid;
+	int numstreams;
+	int readstream_index;
+	char readstream[AST_MAX_EXTENSION];
+	char writestream[AST_MAX_EXTENSION];
+
+	/* \brief Pipe file descriptor handles array.
+	 * Read from pipe[0], write to pipe[1]
+	 */
+	int pipe[2];
+};
+
+#ifdef LOW_MEMORY 
+static int hash_streamgroups_size = 17;
+static int hash_rtmpmessages_size = 17; 
+#else 
+static int hash_streamgroups_size = 563;
+static int hash_rtmpmessages_size = 563; 
+#endif 
+
+struct ao2_container *streamgroups;
+
+#define rtmp_pvt_lock(x) ao2_lock(x)
+#define rtmp_pvt_trylock(x) ao2_trylock(x)
+#define rtmp_pvt_unlock(x) ao2_unlock(x)
+
+/*! \brief
+ * when we create or delete references, make sure to use these
+ * functions so we keep track of the refcounts.
+ * To simplify the code, we allow a NULL to be passed to streamgroup_unref().
+ */
+#ifdef REF_DEBUG
+#define streamgroup_ref(arg1,arg2) streamgroup_ref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define streamgroup_unref(arg1,arg2) streamgroup_unref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define rtmpmessage_ref(arg1,arg2) rtmpmessage_ref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define rtmpmessage_unref(arg1,arg2) rtmpmessage_unref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
+
+static struct rtmp_pvt *streamgroup_ref_debug(struct rtmp_pvt *p, char *tag, char *file, int line, const char *func)
+{
+	if (p)
+		__ao2_ref_debug(p, 1, tag, file, line, func);
+	else
+		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+	return p;
+}
+
+static struct rtmp_pvt *streamgroup_unref_debug(struct rtmp_pvt *p, char *tag, char *file, int line, const char *func)
+{
+	if (p)
+		__ao2_ref_debug(p, -1, tag, file, line, func);
+	return NULL;
+}
+
+static struct rtmp_message *rtmpmessage_ref_debug(struct rtmp_message *p, char *tag, char *file, int line, const char *func)
+{
+	if (p)
+		__ao2_ref_debug(p, 1, tag, file, line, func);
+	else
+		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+	return p;
+}
+
+static struct rtmp_message *rtmpmessage_unref_debug(struct rtmp_message *p, char *tag, char *file, int line, const char *func)
+{
+	if (p)
+		__ao2_ref_debug(p, -1, tag, file, line, func);
+	return NULL;
+}
+#else
+static struct rtmp_pvt *streamgroup_ref(struct rtmp_pvt *p, char *tag)
+{
+	if (p)
+		ao2_ref(p, 1);
+	else
+		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+	return p;
+}
+
+static struct rtmp_pvt *streamgroup_unref(struct rtmp_pvt *p, char *tag)
+{
+	if (p)
+		ao2_ref(p, -1);
+	return NULL;
+}
+
+static struct rtmp_message *rtmpmessage_ref(struct rtmp_message *p, char *tag)
+{
+	if (p)
+		ao2_ref(p, 1);
+	else
+		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+	return p;
+}
+
+static struct rtmp_rtmpmessage *rtmpmessage_unref(struct rtmp_message *p, char *tag)
+{
+	if (p)
+		ao2_ref(p, -1);
+	return NULL;
+}
+#endif
+
+/** 
+ * A maximum of 64 simultaneous channels can be supported. Therefore, as each 
+ * stream reserves 5 channels, we have a maximum of 11 concurrent streams 
+ * (channel range 0-3 is reserved).
+ *
+ * The following code is taken from the Red5 server sources. We show it here
+ * to expose how the Red5 server relates stream and channel identifiers
+ * together.
+ * In function getStreamIdForChannel from file RTMPConnection.java :
+ * 	return ((channelId - 4) / 5) + 1;
+ *
+ * Function createOutputStream from file RTMPConnection.java : 
+ * public OutputStream createOutputStream(int streamId) {
+ * 	int channelId = (4 + ((streamId - 1) * 5));
+ *      final Channel data = getChannel(channelId++);
+ *      final Channel video = getChannel(channelId++);
+ *      final Channel audio = getChannel(channelId++);
+ *      // final Channel unknown = getChannel(channelId++);
+ *      // final Channel ctrl = getChannel(channelId++);
+ *      return new OutputStream(video, audio, data);
+ * }
+ */
+ast_mutex_t streamslock;
+static struct rtmp_channel* streams[RTMP_MAX_CHANNELS];
+unsigned int outgoing_chunksize = RTMP_CHUNK_SIZE;
+unsigned int incoming_chunksize = RTMP_CHUNK_SIZE;
+
+static struct ast_channel *rtmp_request(const char *type, int format, void *data, int *cause);
+static int rtmp_call(struct ast_channel *ast, char *dest, int timeout);
+static void rtmp_destroy_fn(void *p);
+static void rtmp_destroy(struct rtmp_pvt *p);
+static void rtmpmessage_destroy_fn(void *p);
+static void rtmpmessage_destroy(struct rtmp_message *rtmp);
+static int rtmp_hangup(struct ast_channel *ast);
+static struct ast_frame *rtmp_read(struct ast_channel *ast);
+static int rtmp_write(struct ast_channel *ast, struct ast_frame *frame);
+static enum ast_bridge_result rtmp_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
+
+static void* rtmp_start(void *data);
+static struct rtmp_pvt* find_streamgroup(struct rtmp_message* rtmp);
+static int check_handshake_reply (void *buffer, size_t size);
+static int rtmp_send_connect(struct rtmp_client *client, char *handshake);
+static int rtmp_send_pong(struct rtmp_client *client, struct rtmp_message *rtmp);
+static int rtmp_send_chunksize(struct rtmp_client *client, uint32_t newchunksize); 
+static int rtmp_send_buffertime(struct rtmp_client *client,  uint32_t streamid); 
+static int rtmp_send_createstream(struct rtmp_client *client, double streamid); 
+static int rtmp_send_closestream(struct rtmp_client *client, double streamid); 
+static int rtmp_send_play(struct rtmp_client *client, uint32_t streamid, char *name);
+static int rtmp_send_publish(struct rtmp_client *client, uint32_t streamid, char *name);
+static int rtmp_send_audio(struct rtmp_client *client, struct rtmp_pvt *p, struct ast_frame *frame); 
+static int amf_add_bobject(struct amf_object *object, uint8_t type, char *property, void *value); 
+static int amf_destroy_object(struct amf_object *object);
+static char* rtmp_build_invoke(struct rtmp_message *rtmp, char *method, double connectionid, struct amf_object *amf, char *options, void *boolean, char *newoptions);
+static char* rtmp_build_audio(struct rtmp_message *rtmp, void* samples, int datalen); 
+static int rtmp_set_header(char *header, struct rtmp_message *rtmp, int hdrlen);
+static int rtmp_set_boolean(void *message, void *value); 
+static int rtmp_set_property(void *message, char *string);
+static int rtmp_set_string(void *message, char *string, size_t length);
+static int rtmp_set_number(void *message, double *number);
+static int rtmp_set_null(void *message); 
+static int rtmp_set_object(void *message, struct amf_object *amf); 
+static int amf_numberlen(double *number);
+static int amf_booleanlen(void *boolean);
+static int amf_strlen(char *string);
+static int amf_objlen(struct amf_object *object);
+static int rtmp_send_message(struct rtmp_client *client, char *prefix, char *message, size_t bodysize); 
+static int rtmp_set_outgoing_channelinfo(struct rtmp_message *rtmp, uint8_t next_hdrlen);
+static int rtmp_set_incoming_channelinfo(void *buffer, int hdrlen, int channelid);
+static int rtmp_get_current_hdrlen(uint8_t channelid); 
+static int rtmp_get_current_timestamp(uint8_t channelid); 
+static int rtmp_get_current_bodylen(uint8_t channelid); 
+static int rtmp_get_current_type(uint8_t channelid); 
+static int rtmp_get_current_streamid(uint8_t channelid); 
+static int rtmp_get_streamid(uint8_t channelid);
+static int rtmp_get_starting_channelid(uint8_t streamid);
+static int rtmp_get_header_length(char *header);
+static int rtmp_get_channelid(char *header);
+static int rtmp_get_bodylen(char *header, struct rtmp_message *rtmp, int direction); 
+static int rtmp_parse_header(struct rtmp_message *rtmp, void *buffer);
+static int rtmp_handle_system_message(struct rtmp_client *client, struct rtmp_message *rtmp);
+static int rtmp_handle_connection_message(struct rtmp_client *client, struct rtmp_message *rtmp);
+static int rtmp_handle_audio_packet(struct rtmp_client *client, struct rtmp_message *rtmp);
+static int amf_parse_reply(double *result, char *level, char *code, char *description, char *amf, size_t len); 
+static int amf_get_type(char *buf); 
+static int amf_get_string(char *string, void* buffer, size_t length);
+static int amf_get_number(double *number, void *amf);
+static int rtmp_new_streamid(int range);
+static int isfree(int streamid, int range);
+static int activate_channels(int channelid, int range); 
+static int desactivate_channels(int channelid, int range);
+
+static const struct ast_channel_tech rtmp_tech = {
+	.type = type,
+	.description = tdesc,
+	.capabilities = AST_FORMAT_SLINEAR,
+	.requester = rtmp_request,
+	.call = rtmp_call,
+	.hangup = rtmp_hangup,
+	.read = rtmp_read,
+	.write = rtmp_write,
+	.bridge = rtmp_bridge,
+};
+
+/*!
+ * \note Use the stream id 
+ */
+static int streamgroups_hash_cb(const void *obj, const int flags) {
+	const struct rtmp_pvt *pvt = obj;
+
+	ast_debug(7, "pvt->streamid = %d\n", pvt->streamid); 
+	return pvt->streamid;
+}
+
+/*!
+ * \note Use the stream id
+ */
+static int streamgroups_cmp_cb(void *obj, void *arg, int flags) {
+	int res = 0;
+	struct rtmp_pvt *pvt = obj, *pvt2 = arg;
+	
+	ast_debug(7, "pvt->streamid = %d - pvt2->streamid = %d - pvt->numstreams = %d\n", pvt->streamid, pvt2->streamid, pvt->numstreams);
+	/* match if streamid <= id < streamid + range */
+	if (pvt2->streamid >= pvt->streamid && pvt2->streamid < pvt->streamid + pvt->numstreams) {
+		res = CMP_MATCH | CMP_STOP;
+	}
+
+	return res;
+}
+
+/*!
+ * \note Use the channel id 
+ */
+static int rtmpmessages_hash_cb(const void *obj, const int flags) {
+	const struct rtmp_message *pvt = obj;
+
+	ast_debug(7, "pvt->channelid = %d\n", pvt->channelid); 
+	return pvt->channelid;
+}
+
+/*!
+ * \note Use the stream id
+ */
+static int rtmpmessages_cmp_cb(void *obj, void *arg, int flags) {
+	int res = 0;
+	struct rtmp_message *pvt = obj, *pvt2 = arg;
+	
+	if (pvt2->channelid == pvt->channelid) {
+		res = CMP_MATCH | CMP_STOP;
+	}
+
+	return res;
+}
+
+static int rtmp_call(struct ast_channel *ast, char *dest, int timeout)
+{
+	struct rtmp_pvt *p;
+	int res = -1;
+	int i;
+
+	p = ast->tech_pvt;
+	if (!p) {
+		ast_debug(3, "tech_pvt is NULL\n");
+	} else {
+		ast_debug(3, "p->owner->name : \%s\n", p->owner->name);
+	}
+
+	if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+		ast_log(LOG_WARNING, "rtmp_call called on %s, neither down nor reserved\n", ast->name);
+		return -1;
+	}
+	/* When we call, it just works, really, there's no destination...  Just
+	   ring the phone and wait for someone to answer */
+	ast_debug(3, "Calling %s on %s\n", dest, ast->name);
+
+	for (i = 0; i < p->numstreams; i++) {
+		/* create at least 2 streams for publish/play */
+		res = rtmp_send_createstream(client, (double)p->streamid + i);
+		if (!res) {
+			ast_log(LOG_WARNING, "Could not create RTMP stream\n");
+			rtmp_hangup(ast);
+			return -1;
+		}
+		ast_verbose("Sending createStream request for stream with id %f\n", (double)p->streamid + i);
+	}
+	return 0;
+}
+
+static void rtmp_destroy_fn(void *p) {
+	rtmp_destroy(p);
+}
+
+static void rtmp_destroy(struct rtmp_pvt *p) {
+	ast_debug(3, "Freeing rtmp_pvt structures\n");
+	close(p->pipe[0]);
+	close(p->pipe[1]);
+	avcodec_close(p->encoding_context);
+	avcodec_close(p->decoding_context);
+	if (p->tortmp_resample_context) {
+		audio_resample_close(p->tortmp_resample_context);
+	}
+	if (p->fromrtmp_resample_context) {
+		audio_resample_close(p->fromrtmp_resample_context);
+	}
+	av_free(p->encoding_context);
+	av_free(p->decoding_context);
+}
+
+static void rtmpmessage_destroy_fn(void *p) {
+	rtmpmessage_destroy(p);
+}
+
+static void rtmpmessage_destroy(struct rtmp_message *rtmp) {
+	ast_free(rtmp->body);
+}
+
+/** \brief Allocate a new RTMP stream
+ * A new RTMP stream consists of 5 RTMP channels
+ */
+static struct rtmp_pvt *rtmp_alloc(char *writestream, char *readstream, char *readnum) {
+	struct rtmp_pvt *p;
+	int rnum = 0;
+
+	if (!readnum) {
+		rnum = 1;
+	} else {
+		rnum = atoi(readnum);
+	}
+
+	if (!(p = ao2_t_alloc(sizeof(*p), rtmp_destroy_fn, "allocate an streamgroup(pvt) struct")))
+		return NULL;
+
+	/* Each rtmp_pvt struct spans over a block of at least 2 RTMP streams :
+	 * the first stream identifier is reserved for publication, the
+	 * following stream identifiers are reserved for stream playback.
+	 * streamid identifies the first stream identifier for this span, 
+	 * numstreams gives the total number of streams.
+	 */
+	p->streamid = rtmp_new_streamid(1 + rnum);
+
+	if (!p->streamid) {
+		ast_log(LOG_WARNING, "Could not find a block of %d streams for this rtmp_pvt\n", 1 + rnum);
+		streamgroup_unref(p, "Released a reference");
+		return NULL;
+	}
+
+	p->numstreams = 1 + rnum;
+	p->readstream_index = 0;
+	p->encoder = NULL;
+	p->decoder = NULL;
+	p->encoding_context = NULL;
+	p->decoding_context = NULL;
+	p->rtmpinputrate = 11000;
+	p->astinputrate = 8000;
+
+	/* the outputrate value of this context matches with the sampling
+	 * rate of the RTMP packets that come in to Asterisk. On the other
+	 * hand, the inputrate value of this context matches with the 
+	 * sampling rate of the packets that come in to Asterisk from the
+	 * opposite Asterisk channel (eg : RTP packets).
+	 * Other values are taken from the examples given in FFMPEG.
+	 * The function prototype is :
+	 * ReSampleContext *av_audio_resample_init(int output_channels, int input_channels,
+         * 		                           int output_rate, int input_rate,
+         * 			    		   enum SampleFormat sample_fmt_out,
+         *                             		   enum SampleFormat sample_fmt_in,
+         *                               	   int filter_length, int log2_phase_count,
+         *                               	   int linear, double cutoff)
+	 */
+	p->tortmp_resample_context = av_audio_resample_init(
+					1, 1,
+					p->rtmpinputrate, p->astinputrate,
+					SAMPLE_FMT_S16, SAMPLE_FMT_S16,
+					16, 10, 1, 0.8); 
+	/* see the comment above */
+	p->fromrtmp_resample_context = av_audio_resample_init(
+					1, 1,
+					p->astinputrate, p->rtmpinputrate,
+					SAMPLE_FMT_S16, SAMPLE_FMT_S16,
+					16, 10, 1, 0.8); 
+
+	strncpy(p->readstream, readstream, AST_MAX_EXTENSION);
+	strncpy(p->writestream, writestream, AST_MAX_EXTENSION);
+
+	/* add to active RTMP streams list */
+	ao2_t_link(streamgroups, p, "link pvt into RTMP streams table");
+
+	return p;
+}
+
+static int rtmp_hangup(struct ast_channel *ast) {
+	struct rtmp_pvt *p;
+	int i;
+
+	p = ast->tech_pvt;
+
+	for (i = 0; i < p->numstreams; i++) {
+		rtmp_send_closestream(client, p->streamid + i); 
+	}
+
+	ast_debug(3, "rtmp_hangup(%s)\n", ast->name);
+	if (!ast->tech_pvt) {
+		ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+		return 0;
+	}
+
+	desactivate_channels(rtmp_get_starting_channelid(p->streamid), p->numstreams);
+
+	streamgroup_ref(p, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+	ast_debug(3, "Deleting rtmp_pvt message from list\n");
+	ao2_t_unlink(streamgroups, p, "unlinking RTMP streams group via ao2_unlink");
+	streamgroup_unref(p, "Dereferecing RTMP streams group after it has been unlinked");
+	streamgroup_unref(p, "Closing stream");
+
+	ast->tech_pvt = NULL;
+	ast_setstate(ast, AST_STATE_DOWN);
+	return 0;
+}
+
+static struct ast_frame *rtmp_read(struct ast_channel *ast) {
+	struct rtmp_pvt *p = ast->tech_pvt;
+	int res;
+	char *buf = NULL;
+	static struct ast_frame f;
+	
+	buf = ast_malloc(4096);
+	if (!buf) {
+		return NULL;
+	}
+
+	f.frametype = AST_FRAME_NULL;
+	f.subclass = 0;
+	f.samples = 0;
+	f.datalen = 0;
+	f.data.ptr = NULL;
+	f.offset = 0;
+	f.src = "RTMP";
+	f.mallocd = 0;
+	f.delivery.tv_sec = 0;
+	f.delivery.tv_usec = 0;
+
+	res = read(p->pipe[0], buf, 4096);
+	if (!res) {
+		ast_log(LOG_ERROR, "Failed to read frame from channel %s\n", ast->name);
+		return &f;
+	}
+
+	f.frametype = AST_FRAME_VOICE;
+	f.subclass = AST_FORMAT_SLINEAR;
+	f.samples = res / 2;
+	f.datalen = res;
+	f.data.ptr = buf;
+
+	ast_debug(7, "Read %d bytes as a frame on %s\n", res, ast->name);
+
+	ast_free(buf);
+	return &f;
+}
+
+static int rtmp_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+	struct rtmp_pvt *p = ast->tech_pvt;
+	int res = -1;
+
+	if (frame->frametype != AST_FRAME_VOICE && frame->frametype != AST_FRAME_VIDEO) {
+		ast_log(LOG_WARNING, "Don't know what to do with  frame type '%d'\n", frame->frametype);
+		return 0;
+	}
+
+	if (frame->frametype == AST_FRAME_VOICE) {
+		if (!(frame->subclass & (AST_FORMAT_SLINEAR))) {
+			ast_log(LOG_WARNING, "Cannot handle frames in %d format\n", frame->subclass);
+			return 0;
+		}
+	}
+
+	if (ast->_state != AST_STATE_UP) {
+		/* Don't try tos end audio on-hook */
+		return 0;
+	}
+
+	if (frame->frametype == AST_FRAME_VOICE) {
+		res = rtmp_send_audio(client, p, frame);	
+	}
+
+	return 0;
+}
+
+static enum ast_bridge_result rtmp_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms) {
+	return 0;
+}
+
+static struct ast_channel *rtmp_new(struct rtmp_pvt *i, int state)
+{
+	struct ast_channel *tmp;
+
+	tmp = ast_channel_alloc(1, state, 0, 0, "", "s", context, 0, "RTMP/%d", i->streamid);
+	if (!tmp) {
+		ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+		return NULL;
+	}
+
+	if (pipe(i->pipe) < 0) {
+		ast_log(LOG_ERROR, "Pipe failed\n");
+	}
+	ast_channel_set_fd(tmp, 0, i->pipe[0]);
+	
+	tmp->tech = &rtmp_tech;
+	tmp->nativeformats = prefformat;
+	tmp->readformat = prefformat;
+	tmp->writeformat = prefformat;
+	if (state == AST_STATE_RING)
+		tmp->rings = 1;
+	tmp->tech_pvt = i;
+	ast_copy_string(tmp->context, context, sizeof(tmp->context));
+	ast_copy_string(tmp->exten, "s",  sizeof(tmp->exten));
+	ast_string_field_set(tmp, language, "");
+	i->owner = tmp;
+	i->encoder = avcodec_find_encoder(CODEC_ID_PCM_S16LE);
+	if (!i->encoder) {
+		ast_debug(3, "Codec not found\n");
+		ast_hangup(tmp);
+	}
+	i->encoding_context = avcodec_alloc_context2(CODEC_ID_ADPCM_SWF);
+	i->encoding_context->channels = 1;
+	i->encoding_context->sample_rate = 11000;
+	if (avcodec_open(i->encoding_context, i->encoder) < 0) {
+		ast_debug(3, "Could not open codec\n");
+		ast_hangup(tmp);
+	}
+
+	i->decoder = avcodec_find_decoder(CODEC_ID_NELLYMOSER);
+	if (!i->decoder) {
+		ast_debug(3, "Codec not found\n");
+		ast_hangup(tmp);
+	}
+	i->decoding_context = avcodec_alloc_context2(CODEC_ID_NELLYMOSER);
+	if (avcodec_open(i->decoding_context, i->decoder) < 0) {
+		ast_debug(3, "Could not open codec\n");
+		ast_hangup(tmp);
+	}
+
+	if (state != AST_STATE_DOWN) {
+		if (ast_pbx_start(tmp)) {
+			ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+			ast_hangup(tmp);
+		}
+	}
+
+	return tmp;
+}
+
+
+static struct ast_channel *rtmp_request(const char *type, int format, void *data, int *cause)
+{
+	int oldformat;
+	struct rtmp_pvt *p;
+	struct ast_channel *tmp = NULL;
+	char *parse;
+
+	AST_DECLARE_APP_ARGS(args,
+			AST_APP_ARG(writestream);
+			AST_APP_ARG(readstream);
+			AST_APP_ARG(readnum);
+			);
+
+	oldformat = format;
+	format &= (AST_FORMAT_SLINEAR);
+	if (!format) {
+		ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+		return NULL;
+	}
+
+	parse = ast_strdupa(data);
+        AST_NONSTANDARD_APP_ARGS(args, parse, '/');
+
+	ast_debug(3, "Building new stream, parse : %s\n", parse);
+	ast_debug(3, "writestream : %s\n", args.writestream);
+	ast_debug(3, "readstream : %s\n", args.readstream);
+	ast_debug(3, "readnum : %s\n", args.readnum);
+	p = rtmp_alloc(args.writestream, args.readstream, args.readnum);
+
+	if (p) {
+		tmp = rtmp_new(p, AST_STATE_DOWN);
+		if (!tmp) {
+			rtmp_destroy(p);
+		}
+		p->owner = tmp;
+	}
+	return tmp;
+}
+
+static void *rtmp_start(void *data) {
+	int res = -1;
+	char *handshake;
+	char *handshake_server;			/* received from server */
+	char *buffer;
+	struct ao2_container *rtmpmessages = NULL;
+	struct ao2_iterator aux;
+	struct rtmp_message *rtmp = NULL;
+
+	buffer = ast_malloc(RTMP_RECV_BUFSIZE);
+	if (!buffer) {
+		return NULL;
+	}
+	handshake = ast_malloc(RTMP_BLOCK_SIZE + 1);
+	if (!handshake) {
+		return NULL;
+	}
+	handshake_server = ast_malloc(RTMP_BLOCK_SIZE);
+	if (!handshake_server) {
+		return NULL;
+	}
+	/* initialize the RTMP messages container */
+	rtmpmessages = ao2_t_container_alloc(hash_rtmpmessages_size, rtmpmessages_hash_cb, rtmpmessages_cmp_cb, "allocate RTMP messages");
+
+	/* set the first byte to 0x03, fill the rest with zeros */
+	memset(handshake, '\0', RTMP_BLOCK_SIZE + 1);
+	handshake[0] = '\3';
+
+
+	ast_debug(3, "Starting thread\n");
+	client->state = RTMP_CONNECTING;
+	client->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (!client->fd) {
+		ast_log(LOG_ERROR, "Unable to build socket\n");
+		return NULL;
+	}
+	if (connect(client->fd, (struct sockaddr *) &rtmpserver, sizeof(rtmpserver)) < 0) {
+		ast_log(LOG_ERROR, "Unable to connect to server\n");
+		return NULL;
+	}
+
+	/* start handshake */
+	if (send(client->fd, handshake, RTMP_BLOCK_SIZE + 1, 0) != RTMP_BLOCK_SIZE + 1) {
+		ast_log(LOG_ERROR, "Could not initiate handshake\n");
+		return NULL;
+	}
+
+	/* wait for the server to reply, check answer */
+	res = recv(client->fd, buffer, 2*RTMP_BLOCK_SIZE + 1, MSG_WAITALL);
+	memcpy(handshake_server, buffer + 1, RTMP_BLOCK_SIZE);
+	client->state = check_handshake_reply(buffer, res);
+
+	/* mark main RTMP channels (0 to 3) as active */
+	activate_channels(0, 0);
+
+	/* send connect message and wait for reply */
+	rtmp_send_connect(client, handshake_server);
+	client->state = RTMP_CONNECTED;
+
+	while(client->fd && res) {
+		int buflen = 0;
+		int channelid = 0;
+		struct rtmp_message mtmp;
+		int newmessage = 0;
+		
+		memset(buffer, '\0', RTMP_RECV_BUFSIZE);
+
+		/* receive first byte to get header length and channel id */
+		res = recv(client->fd, buffer, 1, MSG_WAITALL);
+		if (!res) {
+			ast_log(LOG_WARNING, "RTMP socket closed\n");
+			break;	
+		}
+
+		ast_debug(7, "--------- New RTMP message received --------\n");
+		ast_debug(7, "res = %d\n", res);
+		channelid = rtmp_get_channelid(buffer);
+		mtmp.channelid = channelid;
+		rtmp = ao2_t_find(rtmpmessages, &mtmp, OBJ_POINTER, "ao2 find in rtmpmessages");
+		if (!rtmp) {
+			newmessage = 1;
+			/* need to build a new message and insert it in our list */
+			if (!(rtmp = ao2_t_alloc(sizeof(*rtmp), rtmpmessage_destroy_fn, "allocate RTMP message struct"))) {
+				return NULL;
+			}
+			rtmp->body = ast_malloc(RTMP_MAX_BODYSIZE);
+			if (!rtmp->body) {
+				return NULL;
+			}
+			memset(rtmp->body, '\0', RTMP_MAX_BODYSIZE);
+		}
+
+		rtmp->channelid = rtmp_get_channelid(buffer);
+		rtmp->hdrlen = rtmp_get_header_length(buffer);
+
+		/* retrieve the remaining header bytes */
+		if (rtmp->hdrlen > 1) {
+			ast_debug(7, "hdrlen = %d\n", rtmp->hdrlen);
+			//res = recv(client->fd, buffer, rtmp->hdrlen - 1, 0);
+			res = recv(client->fd, buffer, rtmp->hdrlen - 1, MSG_WAITALL);
+			ast_debug(7, "res = %d\n", res);
+			rtmp_parse_header(rtmp, buffer); 
+			rtmp_set_incoming_channelinfo(buffer, rtmp->hdrlen, rtmp->channelid); 
+		}
+		rtmp->bodysize = rtmp_get_bodylen(buffer, rtmp, RTMP_INCOMING);
+		ast_debug(7, "rtmp->bodysize = %d\n", rtmp->bodysize);
+
+		if (rtmp->bodysize <= incoming_chunksize) {
+			buflen = rtmp->bodysize;
+		} else {
+			if (rtmp->bodysize - rtmp->bytesread <= incoming_chunksize) {
+				buflen = rtmp->bodysize - rtmp->bytesread;
+			} else {
+				buflen = incoming_chunksize;
+			}
+		} 
+		
+		if (!rtmp->bodysize) {
+			/* message has no body */
+			if (!newmessage) {
+				rtmpmessage_ref(rtmp, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+
+				ast_debug(7, "Deleting RTMP message from list\n");
+				ao2_t_unlink(rtmpmessages, rtmp, "unlinking RTMP message via ao2_unlink");
+				rtmpmessage_unref(rtmp, "Dereferecing RTMP message after it has been unlinked");
+			}
+			rtmpmessage_unref(rtmp, "release reference on RTMP message, should be destroyed now");
+			continue;
+		}
+
+		/* retrieve the body parts */
+		res = recv(client->fd, buffer, buflen, 0);
+		//res = recv(client->fd, buffer, buflen, MSG_WAITALL);
+		ast_debug(7, "res = %d\n", res);
+
+		memcpy(rtmp->body + rtmp->bytesread, buffer, buflen);
+		rtmp->bytesread += buflen;
+		ast_debug(7, "rtmp->bytesread = %d\n", rtmp->bytesread);
+
+		if (rtmp->bytesread < rtmp->bodysize) {
+			/* message has been partially retrieved, release
+			 * reference and link it to the messages list if it 
+			 * was not found */
+ 			if (newmessage) {
+				ast_debug(5, "Inserted new RTMP message into list\n");
+				ao2_t_link(rtmpmessages, rtmp, "link into RTMP messages table");
+				rtmpmessage_unref(rtmp, "Released a reference (rtmp_message)");
+			} else {
+				rtmpmessage_unref(rtmp, "Released a reference (rtmp_message)");
+				
+			}
+			continue;
+		}
+
+		if (!streams[rtmp->channelid]) {
+			ast_log(LOG_WARNING, "Ignoring message received on inactive RTMP channel %d\n", rtmp->channelid);
+			continue;
+		} 
+
+		/* message has been completely retrieved, process it */
+
+		if (rtmp->channelid < 4) {
+			/* handle system messages here */
+			switch (rtmp->channelid) {
+				case 0:
+					break;
+				case 1:
+					break;
+				case RTMP_CHANNEL_SYSTEM:
+					rtmp_handle_system_message(client, rtmp);
+					break;
+				case RTMP_CHANNEL_CONNECT:
+					rtmp_handle_connection_message(client, rtmp);
+					break;
+			}
+			/* release the reference on this message, which should be
+			 * destroyed by Asterisk */
+			if (!newmessage) {
+				rtmpmessage_ref(rtmp, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+
+				ast_debug(5, "Deleting RTMP message from list\n");
+				ao2_t_unlink(rtmpmessages, rtmp, "unlinking RTMP message via ao2_unlink");
+				rtmpmessage_unref(rtmp, "Dereferecing RTMP message after it has been unlinked");
+			}
+			rtmpmessage_unref(rtmp, "release reference on RTMP message, should be destroyed now");
+			continue;
+		}
+
+		ast_debug(5, "rtmp->channelid = %d\n", rtmp->channelid);
+
+		switch ((rtmp->channelid + 1) % RTMP_STREAM_CHANNEL_RANGE) {
+
+			case RTMP_CHANNEL_DATA:
+				ast_debug(5, "Received DATA message for channel with id %d\n", rtmp->channelid);
+				/* TODO : properly handle replies to createStream here */
+				/* rtmp_handle_connection_message(client, rtmp); */
+				break;
+			case RTMP_CHANNEL_VIDEO:
+				ast_debug(5, "Received VIDEO message for channel with id %d\n", rtmp->channelid);
+				break;
+			case RTMP_CHANNEL_AUDIO:
+				ast_debug(5, "Received AUDIO message for channel with id %d\n", rtmp->channelid);
+				rtmp_handle_audio_packet(client, rtmp);
+				break;
+			case RTMP_CHANNEL_UNKNOWN:
+				ast_debug(5, "Received UNKNOWN message for channel with id %d\n", rtmp->channelid);
+				break;
+			case RTMP_CHANNEL_CONTROL:
+				ast_debug(5, "Received CONTROL message for channel with id %d\n", rtmp->channelid);
+				break;
+		}
+
+		/* release the reference on this message, which should be
+		 * destroyed by Asterisk */
+		if (!newmessage) {
+			rtmpmessage_ref(rtmp, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+
+			ast_debug(5, "Deleting RTMP message from list\n");
+			ao2_t_unlink(rtmpmessages, rtmp, "unlinking RTMP message via ao2_unlink");
+			rtmpmessage_unref(rtmp, "Dereferecing RTMP message after it has been unlinked");
+		}
+		rtmpmessage_unref(rtmp, "release reference on RTMP message, should be destroyed now");
+	} 
+
+	/* Free the RTMP messages list */
+	aux = ao2_iterator_init(rtmpmessages, 0);
+	while ((rtmp = ao2_t_iterator_next(&aux, "iterate thru RTMP messages"))) {
+		rtmpmessage_unref(rtmp, "toss RTMP message ptr from iterator_next");
+	}
+	ast_free(handshake);
+	ast_free(handshake_server);
+	ast_free(buffer);
+	close(client->fd);
+
+	return 0;
+}
+
+static struct rtmp_pvt* find_streamgroup(struct rtmp_message* rtmp) {
+	struct rtmp_pvt *p = NULL;
+	struct rtmp_pvt tmp_streamgroup;
+	int streamid = -1;
+
+	streamid = rtmp_get_streamid(rtmp->channelid);
+	tmp_streamgroup.streamid = streamid;
+
+	p = ao2_t_find(streamgroups, &tmp_streamgroup, 0, "ao2 find in streamgroups");
+
+	return p;
+}
+
+static int check_handshake_reply(void *buffer, size_t size) {
+	int res = -1;
+	char handshake[2*RTMP_BLOCK_SIZE + 1];
+
+	/* expected reply */
+	memset(&handshake, 0x00, 2*RTMP_BLOCK_SIZE + 1);
+	handshake[0] = 0x03;
+	handshake[4] = 0x01;
+
+	if (!memcmp(handshake, buffer, 2*RTMP_BLOCK_SIZE)) {
+		/* skip last byte because its value is not always the same! */
+		ast_debug(3, "Handshake test passed, buffer size = %d\n", size);
+		res = RTMP_HANDSHAKE_OK;
+	}
+
+	return res;
+}
+
+static int rtmp_send_connect(struct rtmp_client *client, char *handshake) {
+	int res = -1;
+	struct amf_object *object = NULL;
+	struct rtmp_message *rtmp = NULL;
+	void *message = NULL;
+	double videocodecs = 0.0;
+	double audiocodecs = 615.0;
+	char tcUrl[50];
+
+	ast_debug(3, "In rtmp_send_connect\n");
+
+	/* build URL */
+	snprintf(tcUrl, sizeof(tcUrl), "rtmp://%s:%d/%s", rtmpserverstr, port, application);
+
+	object = ast_calloc(1, sizeof(*object));	
+	if (!object) {
+		ast_log(LOG_ERROR, "Memory allocation failure\n");
+		return res;
+	}
+	rtmp = ast_calloc(1, sizeof(*rtmp));	
+	if (!rtmp) {
+		ast_log(LOG_ERROR, "Memory allocation failure\n");
+		return res;
+	}
+
+	/* build header */
+	rtmp->hdrlen = 12;
+	rtmp->type = RTMP_TYPE_INVOKE;
+	rtmp->channelid = RTMP_CHANNEL_CONNECT;
+	rtmp->timestamp = 0;
+	rtmp->streamid = 0;
+
+	object->size = 0;
+
+	/* Populate the AMF object
+	 * In the case of the INVOKE method, the AMF object must contain
+	 * the following basic objects :
+	 * - app : the application identifier to connect to
+	 * - swfUul : referrer to the SWF file
+	 * - tcUrl : the RTMP URL
+	 * - flashVer : agent version
+	 * - audioCodecs
+	 * - videoCodecs
+	 * - page URL 
+	 * Property names are case sensitive */
+	amf_add_bobject(object, AMF_TYPE_STRING, "app", application);
+	amf_add_bobject(object, AMF_TYPE_STRING, "flashVer", "LNX 9,0,31,0");
+	//	amf_add_bobject(object, AMF_TYPE_STRING, "swfUrl", "http://xcom.inria.fr/TestVideo/TestVideo.swf");
+	amf_add_bobject(object, AMF_TYPE_STRING, "tcUrl", tcUrl);
+	amf_add_bobject(object, AMF_TYPE_BOOLEAN, "fpad", AMF_BOOLEAN_FALSE);
+	amf_add_bobject(object, AMF_TYPE_NUMBER, "audioCodecs", &audiocodecs);
+	amf_add_bobject(object, AMF_TYPE_NUMBER, "videoCodecs", &videocodecs);
+	//	amf_add_bobject(object, AMF_TYPE_STRING, "pageUrl", "http://xcom.inria.fr/TestVideo/TestVideo.html");
+
+	ast_debug(3, "Calling rtmp_send_invoke\n");
+	message = rtmp_build_invoke(rtmp, "connect", 1.0, object, NULL, NULL, NULL);
+	if (!message) {
+		ast_log(LOG_ERROR, "Could not set buffer\n");
+		goto safeout;
+	}
+
+	res = rtmp_send_message(client, handshake, message, rtmp->bodysize);
+	ast_debug(3, "Sent %d bytes to server\n", res);
+
+safeout:
+	amf_destroy_object(object);
+	ast_free(rtmp);
+	ast_free(message);
+	return res;
+}
+
+/** \brief Send PONG message back to server
+ * \param rtmp the received PING message 
+ * \note body size is the same in both directions 
+ * 
+ */
+static int rtmp_send_pong(struct rtmp_client *client, struct rtmp_message *rtmp) {
+	int res = -1;
+	void *message = NULL;
+	void *aux = NULL;
+	uint16_t pingtype = htons(RTMP_PING_TYPE_PONG);
+	struct rtmp_message *pong = NULL;
+	int hdrlen = 0;
+	int msglen = 0;
+	int current_bodylen = 0;
+	int current_type = 0;
+
+
+	pong = ast_calloc(1, sizeof(*pong));	
+	if (!pong) {
+		ast_log(LOG_ERROR, "Memory allocation failure\n");
+		return res;
+	}
+	/* populate our PONG packet */
+	pong->channelid = RTMP_CHANNEL_SYSTEM;
+	pong->bodysize = RTMP_PING_DEFAULTBODYSIZE;
+	pong->type = RTMP_TYPE_PING;
+	pong->streamid = 0;
+	pong->timestamp = 0;
+
+	current_bodylen = rtmp_get_current_bodylen(rtmp->channelid);
+	current_type = rtmp_get_current_type(rtmp->channelid);
+	rtmp_get_current_timestamp(rtmp->channelid);
+	rtmp_get_current_streamid(rtmp->channelid);
+
+
+	if (pong->type != current_type || pong->bodysize != current_bodylen) {
+		hdrlen = 12;
+	} else {
+		hdrlen = 1;
+	}
+
+	pong->hdrlen = hdrlen;
+	msglen = hdrlen + RTMP_PING_DEFAULTBODYSIZE;
+
+	message = ast_calloc(1, msglen);
+	if (!message) {
+		res = -1;
+		goto safeout;
+	}
+
+	aux = message;		
+	res = rtmp_set_header(aux, pong, hdrlen);
+	if (!res) {
+		ast_log(LOG_ERROR, "Error while setting header\n");
+		return res;
+	}
+
+	aux += res;
+	/* set ping type (2 bytes long) */
+	memcpy(aux, &pingtype, 2);
+	aux += 2;
+
+	/* set timestamp (4 bytes long) */
+	memcpy(aux, rtmp->body + 2, 4);
+
+	res = rtmp_send_message(client, NULL, message, pong->bodysize);
+
+safeout:
+	ast_free(pong);
+	ast_free(message);
+	return res;
+}
+
+/** \brief Send CHUNKSIZE message to server
+ */
+static int rtmp_send_chunksize(struct rtmp_client *client, uint32_t newchunksize) {
+	int res = -1;
+	void *message = NULL;
+	void *aux = NULL;
+	struct rtmp_message *rtmp = NULL;
+	int hdrlen = 0;
+	int msglen = 0;
+	int current_bodysize = 0;
+	int current_type = 0;
+	int current_streamid = 0;
+	int current_timestamp = 0;
+
+	newchunksize = htonl(newchunksize);
+
+	rtmp = ast_calloc(1, sizeof(*rtmp));	
+	if (!rtmp) {
+		ast_log(LOG_ERROR, "Memory allocation failure\n");
+		return res;
+	}
+	/* populate our packet */
+	rtmp->channelid = RTMP_CHANNEL_SYSTEM;
+	rtmp->bodysize = sizeof(newchunksize);
+	rtmp->type = RTMP_TYPE_CHUNK_SIZE;
+	rtmp->streamid = 0;
+	rtmp->timestamp = 0;
+
+	current_bodysize = rtmp_get_current_bodylen(rtmp->channelid);
+	current_type = rtmp_get_current_type(rtmp->channelid);
+	current_timestamp = rtmp_get_current_timestamp(rtmp->channelid);
+	current_streamid = rtmp_get_current_streamid(rtmp->channelid);
+
+	if (rtmp->streamid != current_streamid) {
+		hdrlen = 12;
+	} else if (rtmp->type != current_type || rtmp->bodysize != current_bodysize) {
+		hdrlen = 8;
+	} else if (rtmp->timestamp != current_timestamp) {
+		hdrlen = 4;
+	} else {
+		hdrlen = 1;
+	}
+
+	rtmp->hdrlen = hdrlen;
+	msglen = hdrlen + rtmp->bodysize;
+
+	message = ast_calloc(1, msglen);
+	if (!message) {
+		res = -1;
+		goto safeout;
+	}
+
+	aux = message;		
+	res = rtmp_set_header(aux, rtmp, hdrlen);
+	if (!res) {
+		ast_log(LOG_ERROR, "Error while setting header\n");
+		return res;
+	}
+
+	aux += res;
+
+	/* set chunksize (4 bytes long, low-endian) */
+	memcpy(aux, &newchunksize, sizeof(newchunksize));
+
+	res = rtmp_send_message(client, NULL, message, rtmp->bodysize);
+
+safeout:
+	ast_free(rtmp);
+	ast_free(message);
+	return res;
+}
+
+/** \brief Send buffer time to server */
+static int rtmp_send_buffertime(struct rtmp_client *client,  uint32_t streamid) {
+	int res = -1;
+	char *message = NULL;
+	char *aux = NULL;
+	uint16_t pingtype = htons(RTMP_PING_TYPE_TIME);
+	struct rtmp_message buffertime;
+	int hdrlen = 0;
+	int msglen = 0;
+
+	/* populate our PING packet */
+	buffertime.channelid = RTMP_CHANNEL_SYSTEM;
+	buffertime.bodysize = 2 + 4 + 4;
+	buffertime.type = RTMP_TYPE_PING;
+	buffertime.streamid = streamid;
+	buffertime.timestamp = 0;
+
+	hdrlen = 12;
+	msglen = hdrlen + buffertime.bodysize;
+
+	message = ast_calloc(1, msglen);
+	if (!message) {
+		return -1;
+	}
+
+	aux = message;		
+	res = rtmp_set_header(message, &buffertime, hdrlen);
+	if (!res) {
+		ast_log(LOG_ERROR, "Error while setting header\n");
+		return res;
+	}
+
+	aux += res;
+	/* set ping type (2 bytes long) */
+	memcpy(aux, &pingtype, 2);
+	aux += 2;
+
+	/* set timestamp (4 bytes long) */
+	memcpy(aux, &buffertime.streamid, 4);
+	aux += 4;
+
+	/* set buffer time to zero */
+	memset(aux, '\0', 4);
+
+	res = rtmp_set_outgoing_channelinfo(&buffertime, 8);
+
+	res = send(client->fd, message, msglen, 0);
+
+
+	return res;
+}
+
+/* \brief Send a message to create a new stream
+ *
+ * The Action Script function prototype is : 
+ * createstream(double ClientStream, NULL)
+ */

[... 2114 lines stripped ...]



More information about the svn-commits mailing list