|
|
/*
|
|
|
* Copyright (C) 2011 Raphael Coeffic
|
|
|
*
|
|
|
* For the code parts originating from rtmpdump (rtmpsrv.c):
|
|
|
* Copyright (C) 2009 Andrej Stepanchuk
|
|
|
* Copyright (C) 2009 Howard Chu
|
|
|
*
|
|
|
* This file is part of SEMS, a free SIP media server.
|
|
|
*
|
|
|
* SEMS is free software; you can redistribute it and/or modify
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
* (at your option) any later version. This program is released under
|
|
|
* the GPL with the additional exemption that compiling, linking,
|
|
|
* and/or using OpenSSL is allowed.
|
|
|
*
|
|
|
* For a license to use the SEMS software under conditions
|
|
|
* other than those described here, or to purchase support for this
|
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
|
* info@iptel.org
|
|
|
*
|
|
|
* SEMS 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 General Public License for more details.
|
|
|
*
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
*/
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
#include <string.h>
|
|
|
|
|
|
#include "RtmpConnection.h"
|
|
|
#include "RtmpSender.h"
|
|
|
#include "RtmpAudio.h"
|
|
|
#include "RtmpSession.h"
|
|
|
#include "RtmpUtils.h"
|
|
|
|
|
|
#include "AmSessionContainer.h"
|
|
|
#include "log.h"
|
|
|
|
|
|
#include "librtmp/log.h"
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
/* interval we disallow duplicate requests, in msec */
|
|
|
#define DUPTIME 5000
|
|
|
|
|
|
|
|
|
RtmpConnection::RtmpConnection(int fd)
|
|
|
: prev_stream_id(0),
|
|
|
play_stream_id(0),
|
|
|
publish_stream_id(0),
|
|
|
sender(NULL),
|
|
|
session(NULL),
|
|
|
registered(false),
|
|
|
di_reg_client(NULL),
|
|
|
rtmp_cfg(NULL)
|
|
|
{
|
|
|
memset(&rtmp,0,sizeof(RTMP));
|
|
|
RTMP_Init(&rtmp);
|
|
|
rtmp.m_sb.sb_socket = fd;
|
|
|
|
|
|
ident = AmSession::getNewId();
|
|
|
di_reg_client = RtmpFactory_impl::instance()->getRegClient();
|
|
|
rtmp_cfg = RtmpFactory_impl::instance()->getConfig();
|
|
|
}
|
|
|
|
|
|
RtmpConnection::~RtmpConnection()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::setSessionPtr(RtmpSession* s)
|
|
|
{
|
|
|
m_session.lock();
|
|
|
DBG("session ptr = 0x%p\n",s);
|
|
|
session = s;
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::run()
|
|
|
{
|
|
|
RTMPPacket packet;
|
|
|
memset(&packet,0,sizeof(RTMPPacket));
|
|
|
|
|
|
DBG("Starting connection (socket=%i)\n",rtmp.m_sb.sb_socket);
|
|
|
|
|
|
if (!RTMP_Serve(&rtmp)) {
|
|
|
ERROR("Handshake failed\n");
|
|
|
RTMP_Close(&rtmp);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// from here on, we send asynchronously
|
|
|
sender = new RtmpSender(&rtmp);
|
|
|
if(!sender){
|
|
|
ERROR("Could not allocate sender.\n");
|
|
|
RTMP_Close(&rtmp);
|
|
|
return;
|
|
|
}
|
|
|
sender->start();
|
|
|
|
|
|
while(RTMP_IsConnected(&rtmp)) {
|
|
|
|
|
|
if(!RTMP_ReadPacket(&rtmp,&packet)) {
|
|
|
if(RTMP_IsTimedout(&rtmp))
|
|
|
continue;
|
|
|
else
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if(!RTMPPacket_IsReady(&packet))
|
|
|
continue;
|
|
|
|
|
|
int err = processPacket(&packet);
|
|
|
RTMPPacket_Free(&packet);
|
|
|
|
|
|
if(err < 0) {
|
|
|
ERROR("could not process packet\n");
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(registered){
|
|
|
RtmpFactory_impl::instance()->removeConnection(ident);
|
|
|
removeRegistration();
|
|
|
registered = false;
|
|
|
}
|
|
|
detachSession();
|
|
|
|
|
|
// terminate the sender thread
|
|
|
sender->stop();
|
|
|
sender->join();
|
|
|
delete sender;
|
|
|
sender = NULL;
|
|
|
|
|
|
RTMP_Close(&rtmp);
|
|
|
DBG("connection closed\n");
|
|
|
AmThreadWatcher::instance()->add(this);
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::on_stop()
|
|
|
{}
|
|
|
|
|
|
int RtmpConnection::processPacket(RTMPPacket* packet)
|
|
|
{
|
|
|
//DBG("received packet type %02X, size %u bytes, Stream ID %i",
|
|
|
// packet->m_packetType, packet->m_nBodySize, packet->m_nInfoField2);
|
|
|
|
|
|
switch (packet->m_packetType) {
|
|
|
case 0x01:
|
|
|
// chunk size
|
|
|
HandleChangeChunkSize(&rtmp, packet);
|
|
|
break;
|
|
|
|
|
|
case 0x03:
|
|
|
// bytes read report
|
|
|
break;
|
|
|
|
|
|
case 0x04:
|
|
|
// ctrl
|
|
|
HandleCtrl(packet);
|
|
|
break;
|
|
|
|
|
|
case 0x05:
|
|
|
// server bw
|
|
|
DBG("Server BW msg not yet supported.\n");
|
|
|
//TODO: HandleServerBW(&rtmp, packet);
|
|
|
break;
|
|
|
|
|
|
case 0x06:
|
|
|
// client bw
|
|
|
DBG("Client BW msg not yet supported.\n");
|
|
|
//TODO: HandleClientBW(&rtmp, packet);
|
|
|
break;
|
|
|
|
|
|
case RTMP_PACKET_TYPE_AUDIO:
|
|
|
// audio data
|
|
|
//
|
|
|
// note(rco): librtmp writes the absolute timestamp into every packet
|
|
|
// after parsing it.
|
|
|
//
|
|
|
//DBG("audio packet: ts = %8i\n",packet->m_nTimeStamp);
|
|
|
rxAudio(packet);
|
|
|
break;
|
|
|
|
|
|
case RTMP_PACKET_TYPE_VIDEO:
|
|
|
// video data
|
|
|
DBG("video packet");
|
|
|
rxVideo(packet);
|
|
|
break;
|
|
|
|
|
|
case 0x0F:
|
|
|
// flex stream send
|
|
|
break;
|
|
|
|
|
|
case 0x10:
|
|
|
// flex shared object
|
|
|
break;
|
|
|
|
|
|
case 0x11:
|
|
|
// flex message
|
|
|
DBG("flex message\n");
|
|
|
if (invoke(packet, 1))
|
|
|
return -1;
|
|
|
break;
|
|
|
|
|
|
case RTMP_PACKET_TYPE_INFO:
|
|
|
// metadata (notify)
|
|
|
break;
|
|
|
|
|
|
case 0x13:
|
|
|
// shared object
|
|
|
break;
|
|
|
|
|
|
case INVOKE_PTYPE:
|
|
|
// invoke
|
|
|
DBG("invoke message\n");
|
|
|
if (invoke(packet, 0))
|
|
|
return -1;
|
|
|
break;
|
|
|
|
|
|
case 0x16:
|
|
|
/* flv */
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
DBG("unknown packet type (0x%02x)",packet->m_packetType);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
// Returns 0 for OK/Failed/error, 1 for 'Stop or Complete'
|
|
|
int
|
|
|
RtmpConnection::invoke(RTMPPacket *packet, unsigned int offset)
|
|
|
{
|
|
|
const char *body;
|
|
|
unsigned int nBodySize;
|
|
|
int ret = 0, nRes;
|
|
|
|
|
|
body = packet->m_body + offset;
|
|
|
nBodySize = packet->m_nBodySize - offset;
|
|
|
|
|
|
if (body[0] != 0x02) // make sure it is a string method name we start with
|
|
|
{
|
|
|
WARN("Sanity failed. no string method in invoke packet");
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
AMFObject obj;
|
|
|
nRes = AMF_Decode(&obj, body, nBodySize, FALSE);
|
|
|
if (nRes < 0)
|
|
|
{
|
|
|
ERROR("error decoding invoke packet");
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
AMF_Dump(&obj);
|
|
|
AVal method;
|
|
|
AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method);
|
|
|
double txn = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1));
|
|
|
DBG("client invoking <%s>\n",method.av_val);
|
|
|
|
|
|
if (AVMATCH(&method, &av_connect))
|
|
|
{
|
|
|
AMFObject cobj;
|
|
|
AVal pname, pval;
|
|
|
int i;
|
|
|
|
|
|
packet->m_body = NULL;
|
|
|
AMFProp_GetObject(AMF_GetProp(&obj, NULL, 2), &cobj);
|
|
|
for (i=0; i<cobj.o_num; i++)
|
|
|
{
|
|
|
pname = cobj.o_props[i].p_name;
|
|
|
pval.av_val = NULL;
|
|
|
pval.av_len = 0;
|
|
|
if (cobj.o_props[i].p_type == AMF_STRING)
|
|
|
pval = cobj.o_props[i].p_vu.p_aval;
|
|
|
if (AVMATCH(&pname, &av_app))
|
|
|
{
|
|
|
rtmp.Link.app = pval;
|
|
|
pval.av_val = NULL;
|
|
|
if (!rtmp.Link.app.av_val)
|
|
|
rtmp.Link.app.av_val = (char*)"";
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_flashVer))
|
|
|
{
|
|
|
rtmp.Link.flashVer = pval;
|
|
|
pval.av_val = NULL;
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_swfUrl))
|
|
|
{
|
|
|
rtmp.Link.swfUrl = pval;
|
|
|
pval.av_val = NULL;
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_tcUrl))
|
|
|
{
|
|
|
rtmp.Link.tcUrl = pval;
|
|
|
pval.av_val = NULL;
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_pageUrl))
|
|
|
{
|
|
|
rtmp.Link.pageUrl = pval;
|
|
|
pval.av_val = NULL;
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_audioCodecs))
|
|
|
{
|
|
|
rtmp.m_fAudioCodecs = cobj.o_props[i].p_vu.p_number;
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_videoCodecs))
|
|
|
{
|
|
|
rtmp.m_fVideoCodecs = cobj.o_props[i].p_vu.p_number;
|
|
|
}
|
|
|
else if (AVMATCH(&pname, &av_objectEncoding))
|
|
|
{
|
|
|
rtmp.m_fEncoding = cobj.o_props[i].p_vu.p_number;
|
|
|
}
|
|
|
}
|
|
|
sender->SendConnectResult(txn);
|
|
|
}
|
|
|
else if (AVMATCH(&method, &av_createStream))
|
|
|
{
|
|
|
sender->SendResultNumber(txn, ++prev_stream_id);
|
|
|
DBG("createStream: new channel %i",prev_stream_id);
|
|
|
}
|
|
|
else if (AVMATCH(&method, &av_getStreamLength))
|
|
|
{
|
|
|
// TODO: find value for live streams (0?, -1?)
|
|
|
sender->SendResultNumber(txn, 0.0);
|
|
|
}
|
|
|
else if (AVMATCH(&method, &av_play))
|
|
|
{
|
|
|
AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &rtmp.Link.playpath);
|
|
|
|
|
|
DBG("playpath = <%.*s>\n",
|
|
|
rtmp.Link.playpath.av_len,
|
|
|
rtmp.Link.playpath.av_val);
|
|
|
|
|
|
if (rtmp.Link.tcUrl.av_len)
|
|
|
{
|
|
|
|
|
|
DBG("tcUrl = <%.*s>\n",
|
|
|
rtmp.Link.tcUrl.av_len,
|
|
|
rtmp.Link.tcUrl.av_val);
|
|
|
|
|
|
if (rtmp.Link.app.av_val)
|
|
|
{
|
|
|
DBG("app = <%.*s>\n",
|
|
|
rtmp.Link.app.av_len,
|
|
|
rtmp.Link.app.av_val);
|
|
|
}
|
|
|
|
|
|
if (rtmp.Link.flashVer.av_val)
|
|
|
{
|
|
|
DBG("flashVer = <%.*s>\n",
|
|
|
rtmp.Link.flashVer.av_len,
|
|
|
rtmp.Link.flashVer.av_val);
|
|
|
}
|
|
|
|
|
|
if (rtmp.Link.swfUrl.av_val)
|
|
|
{
|
|
|
DBG("swfUrl = <%.*s>\n",
|
|
|
rtmp.Link.swfUrl.av_len,
|
|
|
rtmp.Link.swfUrl.av_val);
|
|
|
}
|
|
|
|
|
|
if (rtmp.Link.pageUrl.av_val)
|
|
|
{
|
|
|
DBG("pageUrl = <%.*s>\n",
|
|
|
rtmp.Link.pageUrl.av_len,
|
|
|
rtmp.Link.pageUrl.av_val);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
play_stream_id = packet->m_nInfoField2;
|
|
|
m_session.lock();
|
|
|
if(session) {
|
|
|
session->setPlayStreamID(play_stream_id);
|
|
|
sender->SendStreamBegin();
|
|
|
sender->SendPlayStart();
|
|
|
}
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_publish))
|
|
|
{
|
|
|
DBG("Client is now publishing (Stream ID = %i)\n",
|
|
|
packet->m_nInfoField2);
|
|
|
publish_stream_id = packet->m_nInfoField2;
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_closeStream))
|
|
|
{
|
|
|
DBG("received closeStream with StreamID=%i\n",packet->m_nInfoField2);
|
|
|
stopStream(packet->m_nInfoField2);
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_deleteStream))
|
|
|
{
|
|
|
// - compare StreamID with play_stream_id
|
|
|
// - if matched: stop session
|
|
|
unsigned int stream_id = (unsigned int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
|
|
|
DBG("received deleteStream with StreamID=%i\n",stream_id);
|
|
|
stopStream(stream_id);
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_dial))
|
|
|
{
|
|
|
AVal uri={0,0};
|
|
|
|
|
|
if(obj.o_num > 3){
|
|
|
AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &uri);
|
|
|
}
|
|
|
|
|
|
if(!uri.av_len){
|
|
|
// missing URI parameter
|
|
|
sender->SendErrorResult(txn,"Sono.Call.NoUri");
|
|
|
}
|
|
|
else {
|
|
|
m_session.lock();
|
|
|
|
|
|
if(session){
|
|
|
sender->SendErrorResult(txn,"Sono.Call.Existing");
|
|
|
}
|
|
|
else {
|
|
|
session = startSession(uri.av_val);
|
|
|
if(!session) {
|
|
|
sender->SendErrorResult(txn,"Sono.Call.Failed");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_hangup))
|
|
|
{
|
|
|
disconnectSession();
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_register))
|
|
|
{
|
|
|
if(!registered){
|
|
|
if(RtmpFactory_impl::instance()->addConnection(ident,this) < 0) {
|
|
|
ERROR("could not register RTMP connection (ident='%s')\n",ident.c_str());
|
|
|
sender->SendErrorResult(txn,"Sono.Registration.Failed");
|
|
|
}
|
|
|
else {
|
|
|
registered = true;
|
|
|
DBG("RTMP connection registered (ident='%s')\n",ident.c_str());
|
|
|
sender->SendRegisterResult(txn,ident.c_str());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(registered){
|
|
|
if(di_reg_client &&
|
|
|
!rtmp_cfg->ImplicitRegistrar.empty()) {
|
|
|
createRegistration(rtmp_cfg->ImplicitRegistrar,
|
|
|
ident,rtmp_cfg->FromName);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else if(AVMATCH(&method, &av_accept))
|
|
|
{
|
|
|
m_session.lock();
|
|
|
if(session) {
|
|
|
session->accept();
|
|
|
}
|
|
|
else {
|
|
|
//TODO: send errror
|
|
|
}
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
|
|
|
AMF_Reset(&obj);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
int RtmpConnection::SendCallStatus(int status)
|
|
|
{
|
|
|
return sender->SendCallStatus(status);
|
|
|
}
|
|
|
|
|
|
int RtmpConnection::NotifyIncomingCall(const string& uri)
|
|
|
{
|
|
|
return sender->NotifyIncomingCall(uri);
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::HandleCtrl(const RTMPPacket *packet)
|
|
|
{
|
|
|
short nType = -1;
|
|
|
unsigned int tmp;
|
|
|
if (packet->m_body && packet->m_nBodySize >= 2)
|
|
|
nType = AMF_DecodeInt16(packet->m_body);
|
|
|
DBG("received ctrl. type: %d, len: %d",
|
|
|
nType, packet->m_nBodySize);
|
|
|
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize);*/
|
|
|
|
|
|
if (packet->m_nBodySize >= 6)
|
|
|
{
|
|
|
switch (nType)
|
|
|
{
|
|
|
case 0:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
DBG("Stream Begin %d", tmp);
|
|
|
break;
|
|
|
|
|
|
case 1:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
DBG("Stream EOF %d", tmp);
|
|
|
if (rtmp.m_pausing == 1)
|
|
|
rtmp.m_pausing = 2;
|
|
|
break;
|
|
|
|
|
|
case 2:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
DBG("Stream Dry %d", tmp);
|
|
|
break;
|
|
|
|
|
|
case 3:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
DBG("Stream Ack %d", tmp);
|
|
|
break;
|
|
|
|
|
|
case 4:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
DBG("Stream IsRecorded %d", tmp);
|
|
|
break;
|
|
|
|
|
|
case 6: /* server ping. reply with pong. */
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
DBG("Ping %d", tmp);
|
|
|
sender->SendCtrl(0x07, tmp, 0);
|
|
|
break;
|
|
|
|
|
|
/* FMS 3.5 servers send the following two controls to let the client
|
|
|
* know when the server has sent a complete buffer. I.e., when the
|
|
|
* server has sent an amount of data equal to m_nBufferMS in duration.
|
|
|
* The server meters its output so that data arrives at the client
|
|
|
* in realtime and no faster.
|
|
|
*
|
|
|
* The rtmpdump program tries to set m_nBufferMS as large as
|
|
|
* possible, to force the server to send data as fast as possible.
|
|
|
* In practice, the server appears to cap this at about 1 hour's
|
|
|
* worth of data. After the server has sent a complete buffer, and
|
|
|
* sends this BufferEmpty message, it will wait until the play
|
|
|
* duration of that buffer has passed before sending a new buffer.
|
|
|
* The BufferReady message will be sent when the new buffer starts.
|
|
|
* (There is no BufferReady message for the very first buffer;
|
|
|
* presumably the Stream Begin message is sufficient for that
|
|
|
* purpose.)
|
|
|
*
|
|
|
* If the network speed is much faster than the data bitrate, then
|
|
|
* there may be long delays between the end of one buffer and the
|
|
|
* start of the next.
|
|
|
*
|
|
|
* Since usually the network allows data to be sent at
|
|
|
* faster than realtime, and rtmpdump wants to download the data
|
|
|
* as fast as possible, we use this RTMP_LF_BUFX hack: when we
|
|
|
* get the BufferEmpty message, we send a Pause followed by an
|
|
|
* Unpause. This causes the server to send the next buffer immediately
|
|
|
* instead of waiting for the full duration to elapse. (That's
|
|
|
* also the purpose of the ToggleStream function, which rtmpdump
|
|
|
* calls if we get a read timeout.)
|
|
|
*
|
|
|
* Media player apps don't need this hack since they are just
|
|
|
* going to play the data in realtime anyway. It also doesn't work
|
|
|
* for live streams since they obviously can only be sent in
|
|
|
* realtime. And it's all moot if the network speed is actually
|
|
|
* slower than the media bitrate.
|
|
|
*/
|
|
|
case 31:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream BufferEmpty %d", __FUNCTION__, tmp);
|
|
|
if (!(rtmp.Link.lFlags & RTMP_LF_BUFX))
|
|
|
break;
|
|
|
if (!rtmp.m_pausing)
|
|
|
{
|
|
|
rtmp.m_pauseStamp = rtmp.m_channelTimestamp[rtmp.m_mediaChannel];
|
|
|
sender->SendPause(TRUE, rtmp.m_pauseStamp);
|
|
|
rtmp.m_pausing = 1;
|
|
|
}
|
|
|
else if (rtmp.m_pausing == 2)
|
|
|
{
|
|
|
sender->SendPause(FALSE, rtmp.m_pauseStamp);
|
|
|
rtmp.m_pausing = 3;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 32:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream BufferReady %d", __FUNCTION__, tmp);
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
tmp = AMF_DecodeInt32(packet->m_body + 2);
|
|
|
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream xx %d", __FUNCTION__, tmp);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (nType == 0x1A)
|
|
|
{
|
|
|
RTMP_Log(RTMP_LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__);
|
|
|
#ifdef CRYPTO
|
|
|
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
|
|
|
|
|
|
/* respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied */
|
|
|
if (rtmp.Link.SWFSize)
|
|
|
{
|
|
|
SendCtrl(0x1B, 0, 0);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
ERROR("Ignoring SWFVerification request, use --swfVfy!");
|
|
|
}
|
|
|
#else
|
|
|
ERROR("Ignoring SWFVerification request, no CRYPTO support!");
|
|
|
#endif
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
//#define DUMP_AUDIO 1
|
|
|
|
|
|
#ifdef DUMP_AUDIO
|
|
|
static void dump_audio(RTMPPacket *packet)
|
|
|
{
|
|
|
static int dump_fd=0;
|
|
|
if(dump_fd == 0){
|
|
|
dump_fd = open("speex_out.raw",O_WRONLY|O_CREAT|O_TRUNC,
|
|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
|
|
|
if(dump_fd < 0)
|
|
|
ERROR("could not open speex_out.raw: %s\n",strerror(errno));
|
|
|
}
|
|
|
if(dump_fd < 0) return;
|
|
|
|
|
|
uint32_t pkg_size = packet->m_nBodySize-1;
|
|
|
write(dump_fd,&pkg_size,sizeof(uint32_t));
|
|
|
write(dump_fd,packet->m_body+1,pkg_size);
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
void RtmpConnection::rxAudio(RTMPPacket *packet)
|
|
|
{
|
|
|
if(!packet) return;
|
|
|
|
|
|
#ifdef DUMP_AUDIO
|
|
|
dump_audio();
|
|
|
#endif
|
|
|
|
|
|
// soundType (byte & 0x01) <20> 0
|
|
|
// 0: mono, 1: stereo
|
|
|
// soundSize (byte & 0x02) <20> 1
|
|
|
// 0: 8-bit, 1: 16-bit
|
|
|
// soundRate (byte & 0x0C) <20> 2
|
|
|
// 0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz
|
|
|
// soundFormat (byte & 0xf0) <20> 4
|
|
|
// 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser, 11: Speex
|
|
|
|
|
|
m_session.lock();
|
|
|
// stream not yet started
|
|
|
if(!session){
|
|
|
m_session.unlock();
|
|
|
return;
|
|
|
}
|
|
|
session->bufferPacket(*packet);
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
|
|
|
RtmpSession* RtmpConnection::startSession(const char* uri)
|
|
|
{
|
|
|
unique_ptr<RtmpSession> n_session(new RtmpSession(this));
|
|
|
AmSipDialog* dialout_dlg = n_session->dlg;
|
|
|
|
|
|
string dialout_id = AmSession::getNewId();
|
|
|
dialout_dlg->setLocalTag(dialout_id);
|
|
|
dialout_dlg->setCallid(AmSession::getNewId());
|
|
|
|
|
|
dialout_dlg->setRemoteParty("<" + string(uri) + ">");
|
|
|
dialout_dlg->setRemoteUri(uri);
|
|
|
|
|
|
string local_party =
|
|
|
"\"" + rtmp_cfg->FromName + "\" <sip:" + ident + "@";
|
|
|
|
|
|
if(!rtmp_cfg->FromDomain.empty()){
|
|
|
local_party += rtmp_cfg->FromDomain;
|
|
|
}
|
|
|
else {
|
|
|
int out_if = dialout_dlg->getOutboundIf();
|
|
|
local_party += AmConfig::SIP_Ifs[out_if].LocalIP;
|
|
|
if(AmConfig::SIP_Ifs[out_if].LocalPort != 5060)
|
|
|
local_party +=
|
|
|
":" + int2str(AmConfig::SIP_Ifs[out_if].LocalPort);
|
|
|
}
|
|
|
|
|
|
local_party += ">";
|
|
|
dialout_dlg->setLocalParty(local_party);
|
|
|
|
|
|
n_session->setCallgroup(dialout_id);
|
|
|
switch(AmSessionContainer::instance()->addSession(dialout_id,
|
|
|
n_session.get())){
|
|
|
case AmSessionContainer::ShutDown:
|
|
|
DBG("Server shuting down... do not create a new session.\n");
|
|
|
return NULL;
|
|
|
|
|
|
case AmSessionContainer::AlreadyExist:
|
|
|
DBG("Session already exist !?\n");
|
|
|
return NULL;
|
|
|
|
|
|
case AmSessionContainer::Inserted:
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
AmMimeBody sdp_body;
|
|
|
sdp_body.addPart(SIP_APPLICATION_SDP);
|
|
|
|
|
|
RtmpSession* pn_session = n_session.release();
|
|
|
if(dialout_dlg->sendRequest(SIP_METH_INVITE,&sdp_body) < 0) {
|
|
|
ERROR("dialout_dlg->sendRequest() returned an error\n");
|
|
|
AmSessionContainer::instance()->destroySession(pn_session);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
pn_session->start();
|
|
|
return pn_session;
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::detachSession()
|
|
|
{
|
|
|
m_session.lock();
|
|
|
DBG("detaching session: erasing session ptr... (s=%p)\n",session);
|
|
|
|
|
|
if(session){
|
|
|
session->setConnectionPtr(NULL);
|
|
|
session = NULL;
|
|
|
}
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::disconnectSession()
|
|
|
{
|
|
|
m_session.lock();
|
|
|
if(session){
|
|
|
session->disconnect();
|
|
|
}
|
|
|
m_session.unlock();
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::createRegistration(const string& domain,
|
|
|
const string& user,
|
|
|
const string& display_name,
|
|
|
const string& auth_user,
|
|
|
const string& passwd)
|
|
|
{
|
|
|
if(!di_reg_client) return;
|
|
|
|
|
|
AmArg di_args,ret;
|
|
|
di_args.push(domain.c_str());
|
|
|
di_args.push(user.c_str());
|
|
|
di_args.push(display_name.c_str()); // display name
|
|
|
di_args.push(auth_user.c_str()); // auth_user
|
|
|
di_args.push(passwd.c_str()); // pwd
|
|
|
di_args.push(FACTORY_Q_NAME);
|
|
|
di_args.push("");
|
|
|
di_args.push("");
|
|
|
di_args.push(ident.c_str());
|
|
|
|
|
|
di_reg_client->invoke("createRegistration", di_args, ret);
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::removeRegistration()
|
|
|
{
|
|
|
if(!di_reg_client || ident.empty()) return;
|
|
|
AmArg di_args,ret;
|
|
|
di_args.push(ident.c_str());
|
|
|
di_reg_client->invoke("removeRegistration", di_args, ret);
|
|
|
}
|
|
|
|
|
|
void RtmpConnection::stopStream(unsigned int stream_id)
|
|
|
{
|
|
|
if(stream_id == play_stream_id){
|
|
|
play_stream_id = 0;
|
|
|
sender->SendStreamEOF();
|
|
|
sender->SendPlayStop();
|
|
|
}
|
|
|
else if(stream_id == publish_stream_id) {
|
|
|
publish_stream_id = 0;
|
|
|
}
|
|
|
}
|