[asterisk-commits] phsultan: branch phsultan/rtmp-support r205765 - in /team/phsultan/rtmp-suppo...
SVN commits to the Asterisk project
asterisk-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 asterisk-commits
mailing list