2009-07-31 08:49:36 +02:00
/*
* RTMP network protocol
* Copyright ( c ) 2009 Kostya Shishkov
*
2011-03-18 18:35:10 +01:00
* This file is part of Libav .
2009-07-31 08:49:36 +02:00
*
2011-03-18 18:35:10 +01:00
* Libav is free software ; you can redistribute it and / or
2009-07-31 08:49:36 +02:00
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
2011-03-18 18:35:10 +01:00
* Libav is distributed in the hope that it will be useful ,
2009-07-31 08:49:36 +02:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
2011-03-18 18:35:10 +01:00
* License along with Libav ; if not , write to the Free Software
2009-07-31 08:49:36 +02:00
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
2010-04-20 16:45:34 +02:00
* @ file
2009-07-31 08:49:36 +02:00
* RTMP protocol
*/
# include "libavcodec/bytestream.h"
# include "libavutil/avstring.h"
2011-11-27 15:04:16 +01:00
# include "libavutil/intfloat.h"
2009-07-31 08:49:36 +02:00
# include "libavutil/lfg.h"
2012-04-15 21:49:48 +02:00
# include "libavutil/opt.h"
2009-07-31 08:49:36 +02:00
# include "libavutil/sha.h"
# include "avformat.h"
2010-03-15 00:59:48 +01:00
# include "internal.h"
2009-07-31 08:49:36 +02:00
# include "network.h"
# include "flv.h"
# include "rtmp.h"
2012-07-19 14:13:58 +02:00
# include "rtmpcrypt.h"
2009-07-31 08:49:36 +02:00
# include "rtmppkt.h"
2011-03-31 16:25:10 +02:00
# include "url.h"
2009-07-31 08:49:36 +02:00
2009-12-11 18:13:35 +01:00
//#define DEBUG
2012-04-15 21:49:48 +02:00
# define APP_MAX_LENGTH 128
2012-04-15 21:50:50 +02:00
# define PLAYPATH_MAX_LENGTH 256
2012-05-09 02:12:14 +02:00
# define TCURL_MAX_LENGTH 512
2012-05-09 02:12:15 +02:00
# define FLASHVER_MAX_LENGTH 64
2012-04-15 21:49:48 +02:00
2009-07-31 08:49:36 +02:00
/** RTMP protocol handler state */
typedef enum {
STATE_START , ///< client has not done anything yet
STATE_HANDSHAKED , ///< client has performed handshake
2009-12-04 17:52:16 +01:00
STATE_RELEASING , ///< client releasing stream before publish it (for output)
STATE_FCPUBLISH , ///< client FCPublishing stream (for output)
2009-07-31 08:49:36 +02:00
STATE_CONNECTING , ///< client connected to server successfully
STATE_READY , ///< client has sent all needed commands and waits for server reply
STATE_PLAYING , ///< client has started receiving multimedia data from server
2009-12-04 17:52:16 +01:00
STATE_PUBLISHING , ///< client has started sending multimedia data to server (for output)
2009-12-11 12:37:21 +01:00
STATE_STOPPED , ///< the broadcast has been stopped
2009-07-31 08:49:36 +02:00
} ClientState ;
/** protocol handler context */
typedef struct RTMPContext {
2012-04-15 21:49:48 +02:00
const AVClass * class ;
2009-07-31 08:49:36 +02:00
URLContext * stream ; ///< TCP stream used in interactions with RTMP server
RTMPPacket prev_pkt [ 2 ] [ RTMP_CHANNELS ] ; ///< packet history used when reading and sending packets
int chunk_size ; ///< size of the chunks RTMP packets are divided into
2009-12-02 13:55:10 +01:00
int is_input ; ///< input/output flag
2012-04-15 21:50:50 +02:00
char * playpath ; ///< stream identifier to play (with possible "mp4:" prefix)
2012-05-05 19:33:26 +02:00
int live ; ///< 0: recorded, -1: live, -2: both
2012-04-15 21:49:48 +02:00
char * app ; ///< name of application
2012-06-08 13:16:34 +02:00
char * conn ; ///< append arbitrary AMF data to the Connect message
2009-07-31 08:49:36 +02:00
ClientState state ; ///< current state
int main_channel_id ; ///< an additional channel ID which is used for some invocations
uint8_t * flv_data ; ///< buffer with data for demuxer
int flv_size ; ///< current buffer size
int flv_off ; ///< number of bytes read from current buffer
2012-06-18 14:55:55 +02:00
int flv_nb_packets ; ///< number of flv packets published
2009-12-04 17:52:16 +01:00
RTMPPacket out_pkt ; ///< rtmp packet, created from flv a/v or metadata (for output)
2010-02-18 17:27:18 +01:00
uint32_t client_report_size ; ///< number of bytes after which client should report to server
uint32_t bytes_read ; ///< number of bytes read from server
uint32_t last_bytes_read ; ///< number of bytes read last reported to server
2011-09-20 15:00:52 +02:00
int skip_bytes ; ///< number of bytes to skip from the input FLV stream in the next write call
2011-09-21 22:21:30 +02:00
uint8_t flv_header [ 11 ] ; ///< partial incoming flv packet header
int flv_header_bytes ; ///< number of initialized bytes in flv_header
2011-11-12 22:28:58 +01:00
int nb_invokes ; ///< keeps track of invoke messages
2011-12-05 11:35:06 +01:00
int create_stream_invoke ; ///< invoke id for the create stream command
2012-05-09 02:12:14 +02:00
char * tcurl ; ///< url of the target stream
2012-05-09 02:12:15 +02:00
char * flashver ; ///< version of the flash plugin
2012-05-09 02:12:16 +02:00
char * swfurl ; ///< url of the swf player
2012-07-24 16:29:40 +02:00
char * pageurl ; ///< url of the web page
2012-06-13 14:47:26 +02:00
int server_bw ; ///< server bandwidth
2012-06-13 14:48:02 +02:00
int client_buffer_time ; ///< client buffer time in ms
2012-06-18 14:55:55 +02:00
int flush_interval ; ///< number of packets flushed in the same request (RTMPT only)
2012-07-19 14:13:58 +02:00
int encrypted ; ///< use an encrypted connection (RTMPE only)
2009-07-31 08:49:36 +02:00
} RTMPContext ;
# define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing
/** Client key used for digest signing */
static const uint8_t rtmp_player_key [ ] = {
' G ' , ' e ' , ' n ' , ' u ' , ' i ' , ' n ' , ' e ' , ' ' , ' A ' , ' d ' , ' o ' , ' b ' , ' e ' , ' ' ,
' F ' , ' l ' , ' a ' , ' s ' , ' h ' , ' ' , ' P ' , ' l ' , ' a ' , ' y ' , ' e ' , ' r ' , ' ' , ' 0 ' , ' 0 ' , ' 1 ' ,
0xF0 , 0xEE , 0xC2 , 0x4A , 0x80 , 0x68 , 0xBE , 0xE8 , 0x2E , 0x00 , 0xD0 , 0xD1 , 0x02 ,
0x9E , 0x7E , 0x57 , 0x6E , 0xEC , 0x5D , 0x2D , 0x29 , 0x80 , 0x6F , 0xAB , 0x93 , 0xB8 ,
0xE6 , 0x36 , 0xCF , 0xEB , 0x31 , 0xAE
} ;
# define SERVER_KEY_OPEN_PART_LEN 36 ///< length of partial key used for first server digest signing
/** Key used for RTMP server digest signing */
static const uint8_t rtmp_server_key [ ] = {
' G ' , ' e ' , ' n ' , ' u ' , ' i ' , ' n ' , ' e ' , ' ' , ' A ' , ' d ' , ' o ' , ' b ' , ' e ' , ' ' ,
' F ' , ' l ' , ' a ' , ' s ' , ' h ' , ' ' , ' M ' , ' e ' , ' d ' , ' i ' , ' a ' , ' ' ,
' S ' , ' e ' , ' r ' , ' v ' , ' e ' , ' r ' , ' ' , ' 0 ' , ' 0 ' , ' 1 ' ,
0xF0 , 0xEE , 0xC2 , 0x4A , 0x80 , 0x68 , 0xBE , 0xE8 , 0x2E , 0x00 , 0xD0 , 0xD1 , 0x02 ,
0x9E , 0x7E , 0x57 , 0x6E , 0xEC , 0x5D , 0x2D , 0x29 , 0x80 , 0x6F , 0xAB , 0x93 , 0xB8 ,
0xE6 , 0x36 , 0xCF , 0xEB , 0x31 , 0xAE
} ;
2012-06-08 13:16:34 +02:00
static int rtmp_write_amf_data ( URLContext * s , char * param , uint8_t * * p )
{
2012-06-13 09:51:22 +02:00
char * field , * value ;
2012-06-08 13:16:34 +02:00
char type ;
/* The type must be B for Boolean, N for number, S for string, O for
* object , or Z for null . For Booleans the data must be either 0 or 1 for
* FALSE or TRUE , respectively . Likewise for Objects the data must be
* 0 or 1 to end or begin an object , respectively . Data items in subobjects
* may be named , by prefixing the type with ' N ' and specifying the name
* before the value ( ie . NB : myFlag : 1 ) . This option may be used multiple times
* to construct arbitrary AMF sequences . */
if ( param [ 0 ] & & param [ 1 ] = = ' : ' ) {
type = param [ 0 ] ;
value = param + 2 ;
} else if ( param [ 0 ] = = ' N ' & & param [ 1 ] & & param [ 2 ] = = ' : ' ) {
type = param [ 1 ] ;
2012-06-13 09:51:22 +02:00
field = param + 3 ;
value = strchr ( field , ' : ' ) ;
if ( ! value )
goto fail ;
* value = ' \0 ' ;
value + + ;
2012-06-08 13:16:34 +02:00
if ( ! field | | ! value )
goto fail ;
ff_amf_write_field_name ( p , field ) ;
} else {
goto fail ;
}
switch ( type ) {
case ' B ' :
ff_amf_write_bool ( p , value [ 0 ] ! = ' 0 ' ) ;
break ;
case ' S ' :
ff_amf_write_string ( p , value ) ;
break ;
case ' N ' :
ff_amf_write_number ( p , strtod ( value , NULL ) ) ;
break ;
case ' Z ' :
ff_amf_write_null ( p ) ;
break ;
case ' O ' :
if ( value [ 0 ] ! = ' 0 ' )
ff_amf_write_object_start ( p ) ;
else
ff_amf_write_object_end ( p ) ;
break ;
default :
goto fail ;
break ;
}
return 0 ;
fail :
av_log ( s , AV_LOG_ERROR , " Invalid AMF parameter: %s \n " , param ) ;
return AVERROR ( EINVAL ) ;
}
2009-07-31 08:49:36 +02:00
/**
2010-06-30 17:38:06 +02:00
* Generate ' connect ' call and send it to the server .
2009-07-31 08:49:36 +02:00
*/
2012-05-23 22:45:03 +02:00
static int gen_connect ( URLContext * s , RTMPContext * rt )
2009-07-31 08:49:36 +02:00
{
RTMPPacket pkt ;
2012-05-09 02:12:15 +02:00
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 4096 ) ) < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " connect " ) ;
2011-12-05 11:35:06 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-07-31 08:49:36 +02:00
ff_amf_write_object_start ( & p ) ;
ff_amf_write_field_name ( & p , " app " ) ;
2009-12-03 16:47:00 +01:00
ff_amf_write_string ( & p , rt - > app ) ;
2009-07-31 08:49:36 +02:00
2012-05-09 02:12:15 +02:00
if ( ! rt - > is_input ) {
2009-12-04 17:52:16 +01:00
ff_amf_write_field_name ( & p , " type " ) ;
ff_amf_write_string ( & p , " nonprivate " ) ;
}
2009-07-31 08:49:36 +02:00
ff_amf_write_field_name ( & p , " flashVer " ) ;
2012-05-09 02:12:15 +02:00
ff_amf_write_string ( & p , rt - > flashver ) ;
2012-05-09 02:12:16 +02:00
if ( rt - > swfurl ) {
ff_amf_write_field_name ( & p , " swfUrl " ) ;
ff_amf_write_string ( & p , rt - > swfurl ) ;
}
2009-07-31 08:49:36 +02:00
ff_amf_write_field_name ( & p , " tcUrl " ) ;
2012-05-09 02:12:14 +02:00
ff_amf_write_string ( & p , rt - > tcurl ) ;
2009-12-04 17:52:16 +01:00
if ( rt - > is_input ) {
2009-12-04 17:52:42 +01:00
ff_amf_write_field_name ( & p , " fpad " ) ;
ff_amf_write_bool ( & p , 0 ) ;
ff_amf_write_field_name ( & p , " capabilities " ) ;
ff_amf_write_number ( & p , 15.0 ) ;
2012-04-02 22:50:38 +02:00
/* Tell the server we support all the audio codecs except
* SUPPORT_SND_INTEL ( 0x0008 ) and SUPPORT_SND_UNUSED ( 0x0010 )
* which are unused in the RTMP protocol implementation . */
2009-12-04 17:52:42 +01:00
ff_amf_write_field_name ( & p , " audioCodecs " ) ;
2012-04-02 22:50:38 +02:00
ff_amf_write_number ( & p , 4071.0 ) ;
2009-12-04 17:52:42 +01:00
ff_amf_write_field_name ( & p , " videoCodecs " ) ;
ff_amf_write_number ( & p , 252.0 ) ;
ff_amf_write_field_name ( & p , " videoFunction " ) ;
ff_amf_write_number ( & p , 1.0 ) ;
2012-07-24 16:29:40 +02:00
if ( rt - > pageurl ) {
ff_amf_write_field_name ( & p , " pageUrl " ) ;
ff_amf_write_string ( & p , rt - > pageurl ) ;
}
2009-12-04 17:52:16 +01:00
}
2009-07-31 08:49:36 +02:00
ff_amf_write_object_end ( & p ) ;
2012-06-08 13:16:34 +02:00
if ( rt - > conn ) {
2012-06-13 09:51:22 +02:00
char * param = rt - > conn ;
2012-06-08 13:16:34 +02:00
// Write arbitrary AMF data to the Connect message.
while ( param ! = NULL ) {
2012-06-13 09:51:22 +02:00
char * sep ;
param + = strspn ( param , " " ) ;
if ( ! * param )
break ;
sep = strchr ( param , ' ' ) ;
if ( sep )
* sep = ' \0 ' ;
2012-06-08 13:16:34 +02:00
if ( ( ret = rtmp_write_amf_data ( s , param , & p ) ) < 0 ) {
// Invalid AMF parameter.
ff_rtmp_packet_destroy ( & pkt ) ;
return ret ;
}
2012-06-13 09:51:22 +02:00
if ( sep )
param = sep + 1 ;
else
break ;
2012-06-08 13:16:34 +02:00
}
}
2009-07-31 08:49:36 +02:00
pkt . data_size = p - pkt . data ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-12-11 16:31:58 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2009-12-04 17:52:16 +01:00
/**
2010-06-30 17:38:06 +02:00
* Generate ' releaseStream ' call and send it to the server . It should make
2009-12-04 17:52:16 +01:00
* the server release some channel for media streams .
*/
2012-05-23 22:45:03 +02:00
static int gen_release_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 17:52:16 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 17:52:16 +01:00
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 29 + strlen ( rt - > playpath ) ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Releasing stream... \n " ) ;
2009-12-04 17:52:16 +01:00
p = pkt . data ;
ff_amf_write_string ( & p , " releaseStream " ) ;
2011-11-12 22:28:58 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 17:52:16 +01:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-12-04 17:52:16 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-12-04 17:52:16 +01:00
}
/**
2010-06-30 17:38:06 +02:00
* Generate ' FCPublish ' call and send it to the server . It should make
2009-12-04 17:52:16 +01:00
* the server preapare for receiving media streams .
*/
2012-05-23 22:45:03 +02:00
static int gen_fcpublish_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 17:52:16 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 17:52:16 +01:00
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 25 + strlen ( rt - > playpath ) ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " FCPublish stream... \n " ) ;
2009-12-04 17:52:16 +01:00
p = pkt . data ;
ff_amf_write_string ( & p , " FCPublish " ) ;
2011-11-12 22:28:58 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 17:52:16 +01:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-12-04 17:52:16 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-12-04 17:52:16 +01:00
}
/**
2010-06-30 17:38:06 +02:00
* Generate ' FCUnpublish ' call and send it to the server . It should make
2009-12-04 17:52:16 +01:00
* the server destroy stream .
*/
2012-05-23 22:45:03 +02:00
static int gen_fcunpublish_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 17:52:16 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 17:52:16 +01:00
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 27 + strlen ( rt - > playpath ) ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " UnPublishing stream... \n " ) ;
2009-12-04 17:52:16 +01:00
p = pkt . data ;
ff_amf_write_string ( & p , " FCUnpublish " ) ;
2011-11-12 22:28:58 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 17:52:16 +01:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-12-04 17:52:16 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-12-04 17:52:16 +01:00
}
2009-07-31 08:49:36 +02:00
/**
2010-06-30 17:38:06 +02:00
* Generate ' createStream ' call and send it to the server . It should make
2009-07-31 08:49:36 +02:00
* the server allocate some channel for media streams .
*/
2012-05-23 22:45:03 +02:00
static int gen_create_stream ( URLContext * s , RTMPContext * rt )
2009-07-31 08:49:36 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-07-31 08:49:36 +02:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Creating stream... \n " ) ;
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 25 ) ) < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " createStream " ) ;
2011-11-12 22:28:58 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 17:52:16 +01:00
ff_amf_write_null ( & p ) ;
2011-12-05 11:35:06 +01:00
rt - > create_stream_invoke = rt - > nb_invokes ;
2009-12-04 17:52:16 +01:00
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-12-04 17:52:16 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-12-04 17:52:16 +01:00
}
/**
2010-06-30 17:38:06 +02:00
* Generate ' deleteStream ' call and send it to the server . It should make
2009-12-04 17:52:16 +01:00
* the server remove some channel for media streams .
*/
2012-05-23 22:45:03 +02:00
static int gen_delete_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 17:52:16 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 17:52:16 +01:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Deleting stream... \n " ) ;
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 34 ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
p = pkt . data ;
ff_amf_write_string ( & p , " deleteStream " ) ;
2011-12-05 11:35:06 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-07-31 08:49:36 +02:00
ff_amf_write_null ( & p ) ;
2009-12-04 17:52:16 +01:00
ff_amf_write_number ( & p , rt - > main_channel_id ) ;
2009-07-31 08:49:36 +02:00
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-07-31 08:49:36 +02:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2012-06-13 14:48:02 +02:00
/**
* Generate client buffer time and send it to the server .
*/
static int gen_buffer_time ( URLContext * s , RTMPContext * rt )
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_PING ,
1 , 10 ) ) < 0 )
return ret ;
p = pkt . data ;
bytestream_put_be16 ( & p , 3 ) ;
bytestream_put_be32 ( & p , rt - > main_channel_id ) ;
bytestream_put_be32 ( & p , rt - > client_buffer_time ) ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
return ret ;
}
2009-07-31 08:49:36 +02:00
/**
2010-06-30 17:38:06 +02:00
* Generate ' play ' call and send it to the server , then ping the server
2009-07-31 08:49:36 +02:00
* to start actual playing .
*/
2012-05-23 22:45:03 +02:00
static int gen_play ( URLContext * s , RTMPContext * rt )
2009-07-31 08:49:36 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-07-31 08:49:36 +02:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Sending play command for '%s' \n " , rt - > playpath ) ;
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_VIDEO_CHANNEL , RTMP_PT_INVOKE ,
0 , 29 + strlen ( rt - > playpath ) ) ) < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
pkt . extra = rt - > main_channel_id ;
p = pkt . data ;
ff_amf_write_string ( & p , " play " ) ;
2011-12-05 11:35:06 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-07-31 08:49:36 +02:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-05-05 19:33:26 +02:00
ff_amf_write_number ( & p , rt - > live ) ;
2009-07-31 08:49:36 +02:00
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-07-31 08:49:36 +02:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-24 13:48:25 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2009-12-04 17:52:16 +01:00
/**
2010-06-30 17:38:06 +02:00
* Generate ' publish ' call and send it to the server .
2009-12-04 17:52:16 +01:00
*/
2012-05-23 22:45:03 +02:00
static int gen_publish ( URLContext * s , RTMPContext * rt )
2009-12-04 17:52:16 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 17:52:16 +01:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Sending publish command for '%s' \n " , rt - > playpath ) ;
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SOURCE_CHANNEL , RTMP_PT_INVOKE ,
0 , 30 + strlen ( rt - > playpath ) ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
pkt . extra = rt - > main_channel_id ;
p = pkt . data ;
ff_amf_write_string ( & p , " publish " ) ;
2011-12-05 11:35:06 +01:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 17:52:16 +01:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
ff_amf_write_string ( & p , " live " ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-12-04 17:52:16 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
return ret ;
2009-12-04 17:52:16 +01:00
}
2009-07-31 08:49:36 +02:00
/**
2010-06-30 17:38:06 +02:00
* Generate ping reply and send it to the server .
2009-07-31 08:49:36 +02:00
*/
2012-05-23 22:45:03 +02:00
static int gen_pong ( URLContext * s , RTMPContext * rt , RTMPPacket * ppkt )
2009-07-31 08:49:36 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_PING ,
ppkt - > timestamp + 1 , 6 ) ) < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
p = pkt . data ;
bytestream_put_be16 ( & p , 7 ) ;
2009-12-16 13:49:38 +01:00
bytestream_put_be32 ( & p , AV_RB32 ( ppkt - > data + 2 ) ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2009-07-31 08:49:36 +02:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2012-03-07 19:21:16 +01:00
/**
* Generate server bandwidth message and send it to the server .
*/
2012-05-23 22:45:03 +02:00
static int gen_server_bw ( URLContext * s , RTMPContext * rt )
2012-03-07 19:21:16 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_SERVER_BW ,
0 , 4 ) ) < 0 )
return ret ;
2012-03-07 19:21:16 +01:00
p = pkt . data ;
2012-06-13 14:47:26 +02:00
bytestream_put_be32 ( & p , rt - > server_bw ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2012-03-07 19:21:16 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2012-03-07 19:21:16 +01:00
}
2012-05-09 00:58:09 +02:00
/**
* Generate check bandwidth message and send it to the server .
*/
2012-05-23 22:45:03 +02:00
static int gen_check_bw ( URLContext * s , RTMPContext * rt )
2012-05-09 00:58:09 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2012-05-09 00:58:09 +02:00
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 21 ) ) < 0 )
return ret ;
2012-05-09 00:58:09 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " _checkbw " ) ;
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
ff_amf_write_null ( & p ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2012-05-09 00:58:09 +02:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
return ret ;
2012-05-09 00:58:09 +02:00
}
2010-02-18 17:27:18 +01:00
/**
2010-06-30 17:38:06 +02:00
* Generate report on bytes read so far and send it to the server .
2010-02-18 17:27:18 +01:00
*/
2012-05-23 22:45:03 +02:00
static int gen_bytes_read ( URLContext * s , RTMPContext * rt , uint32_t ts )
2010-02-18 17:27:18 +01:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_BYTES_READ ,
ts , 4 ) ) < 0 )
return ret ;
2010-02-18 17:27:18 +01:00
p = pkt . data ;
bytestream_put_be32 ( & p , rt - > bytes_read ) ;
2012-05-24 13:48:25 +02:00
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
2010-02-18 17:27:18 +01:00
ff_rtmp_packet_destroy ( & pkt ) ;
2012-05-23 22:45:03 +02:00
2012-05-24 13:48:25 +02:00
return ret ;
2010-02-18 17:27:18 +01:00
}
2012-07-05 13:05:46 +02:00
int ff_rtmp_calc_digest ( const uint8_t * src , int len , int gap ,
const uint8_t * key , int keylen , uint8_t * dst )
2009-07-31 08:49:36 +02:00
{
struct AVSHA * sha ;
uint8_t hmac_buf [ 64 + 32 ] = { 0 } ;
int i ;
sha = av_mallocz ( av_sha_size ) ;
2012-05-23 18:55:34 +02:00
if ( ! sha )
return AVERROR ( ENOMEM ) ;
2009-07-31 08:49:36 +02:00
if ( keylen < 64 ) {
memcpy ( hmac_buf , key , keylen ) ;
} else {
av_sha_init ( sha , 256 ) ;
av_sha_update ( sha , key , keylen ) ;
av_sha_final ( sha , hmac_buf ) ;
}
for ( i = 0 ; i < 64 ; i + + )
hmac_buf [ i ] ^ = HMAC_IPAD_VAL ;
av_sha_init ( sha , 256 ) ;
av_sha_update ( sha , hmac_buf , 64 ) ;
if ( gap < = 0 ) {
av_sha_update ( sha , src , len ) ;
} else { //skip 32 bytes used for storing digest
av_sha_update ( sha , src , gap ) ;
av_sha_update ( sha , src + gap + 32 , len - gap - 32 ) ;
}
av_sha_final ( sha , hmac_buf + 64 ) ;
for ( i = 0 ; i < 64 ; i + + )
hmac_buf [ i ] ^ = HMAC_IPAD_VAL ^ HMAC_OPAD_VAL ; //reuse XORed key for opad
av_sha_init ( sha , 256 ) ;
av_sha_update ( sha , hmac_buf , 64 + 32 ) ;
av_sha_final ( sha , dst ) ;
av_free ( sha ) ;
2012-05-23 18:55:34 +02:00
return 0 ;
2009-07-31 08:49:36 +02:00
}
2012-07-05 13:06:07 +02:00
int ff_rtmp_calc_digest_pos ( const uint8_t * buf , int off , int mod_val ,
int add_val )
{
int i , digest_pos = 0 ;
for ( i = 0 ; i < 4 ; i + + )
digest_pos + = buf [ i + off ] ;
digest_pos = digest_pos % mod_val + add_val ;
return digest_pos ;
}
2009-07-31 08:49:36 +02:00
/**
2010-06-30 17:38:06 +02:00
* Put HMAC - SHA2 digest of packet data ( except for the bytes where this digest
2009-07-31 08:49:36 +02:00
* will be stored ) into that packet .
*
* @ param buf handshake data ( 1536 bytes )
2012-07-19 14:13:58 +02:00
* @ param encrypted use an encrypted connection ( RTMPE )
2009-07-31 08:49:36 +02:00
* @ return offset to the digest inside input data
*/
2012-07-19 14:13:58 +02:00
static int rtmp_handshake_imprint_with_digest ( uint8_t * buf , int encrypted )
2009-07-31 08:49:36 +02:00
{
2012-07-05 13:06:07 +02:00
int ret , digest_pos ;
2009-07-31 08:49:36 +02:00
2012-07-19 14:13:58 +02:00
if ( encrypted )
digest_pos = ff_rtmp_calc_digest_pos ( buf , 772 , 728 , 776 ) ;
else
digest_pos = ff_rtmp_calc_digest_pos ( buf , 8 , 728 , 12 ) ;
2009-07-31 08:49:36 +02:00
2012-07-05 13:05:46 +02:00
ret = ff_rtmp_calc_digest ( buf , RTMP_HANDSHAKE_PACKET_SIZE , digest_pos ,
rtmp_player_key , PLAYER_KEY_OPEN_PART_LEN ,
buf + digest_pos ) ;
2012-05-23 18:55:34 +02:00
if ( ret < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
return digest_pos ;
}
/**
2010-06-30 17:38:06 +02:00
* Verify that the received server response has the expected digest value .
2009-07-31 08:49:36 +02:00
*
* @ param buf handshake data received from the server ( 1536 bytes )
* @ param off position to search digest offset from
* @ return 0 if digest is valid , digest position otherwise
*/
static int rtmp_validate_digest ( uint8_t * buf , int off )
{
uint8_t digest [ 32 ] ;
2012-07-05 13:06:07 +02:00
int ret , digest_pos ;
2009-07-31 08:49:36 +02:00
2012-07-05 13:06:07 +02:00
digest_pos = ff_rtmp_calc_digest_pos ( buf , off , 728 , off + 4 ) ;
2009-07-31 08:49:36 +02:00
2012-07-05 13:05:46 +02:00
ret = ff_rtmp_calc_digest ( buf , RTMP_HANDSHAKE_PACKET_SIZE , digest_pos ,
rtmp_server_key , SERVER_KEY_OPEN_PART_LEN ,
digest ) ;
2012-05-23 18:55:34 +02:00
if ( ret < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
if ( ! memcmp ( digest , buf + digest_pos , 32 ) )
return digest_pos ;
return 0 ;
}
/**
2010-06-30 17:38:06 +02:00
* Perform handshake with the server by means of exchanging pseudorandom data
2009-07-31 08:49:36 +02:00
* signed with HMAC - SHA2 digest .
*
* @ return 0 if handshake succeeds , negative value otherwise
*/
static int rtmp_handshake ( URLContext * s , RTMPContext * rt )
{
AVLFG rnd ;
uint8_t tosend [ RTMP_HANDSHAKE_PACKET_SIZE + 1 ] = {
3 , // unencrypted data
0 , 0 , 0 , 0 , // client uptime
RTMP_CLIENT_VER1 ,
RTMP_CLIENT_VER2 ,
RTMP_CLIENT_VER3 ,
RTMP_CLIENT_VER4 ,
} ;
uint8_t clientdata [ RTMP_HANDSHAKE_PACKET_SIZE ] ;
uint8_t serverdata [ RTMP_HANDSHAKE_PACKET_SIZE + 1 ] ;
int i ;
int server_pos , client_pos ;
2012-07-19 14:13:58 +02:00
uint8_t digest [ 32 ] , signature [ 32 ] ;
int ret , type = 0 ;
2009-07-31 08:49:36 +02:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Handshaking... \n " ) ;
2009-07-31 08:49:36 +02:00
av_lfg_init ( & rnd , 0xDEADC0DE ) ;
// generate handshake packet - 1536 bytes of pseudorandom data
for ( i = 9 ; i < = RTMP_HANDSHAKE_PACKET_SIZE ; i + + )
tosend [ i ] = av_lfg_get ( & rnd ) > > 24 ;
2012-07-19 14:13:58 +02:00
2012-07-24 13:46:28 +02:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 14:13:58 +02:00
/* When the client wants to use RTMPE, we have to change the command
* byte to 0x06 which means to use encrypted data and we have to set
* the flash version to at least 9.0 .115 .0 . */
tosend [ 0 ] = 6 ;
tosend [ 5 ] = 128 ;
tosend [ 6 ] = 0 ;
tosend [ 7 ] = 3 ;
tosend [ 8 ] = 2 ;
/* Initialize the Diffie-Hellmann context and generate the public key
* to send to the server . */
if ( ( ret = ff_rtmpe_gen_pub_key ( rt - > stream , tosend + 1 ) ) < 0 )
return ret ;
}
2012-07-24 13:46:28 +02:00
client_pos = rtmp_handshake_imprint_with_digest ( tosend + 1 , rt - > encrypted ) ;
2012-05-23 18:55:34 +02:00
if ( client_pos < 0 )
return client_pos ;
2009-07-31 08:49:36 +02:00
2012-05-24 13:48:25 +02:00
if ( ( ret = ffurl_write ( rt - > stream , tosend ,
RTMP_HANDSHAKE_PACKET_SIZE + 1 ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Cannot write RTMP handshake request \n " ) ;
return ret ;
}
2012-05-24 13:48:42 +02:00
if ( ( ret = ffurl_read_complete ( rt - > stream , serverdata ,
RTMP_HANDSHAKE_PACKET_SIZE + 1 ) ) < 0 ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_ERROR , " Cannot read RTMP handshake response \n " ) ;
2012-05-24 13:48:42 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2012-05-24 13:48:42 +02:00
if ( ( ret = ffurl_read_complete ( rt - > stream , clientdata ,
RTMP_HANDSHAKE_PACKET_SIZE ) ) < 0 ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_ERROR , " Cannot read RTMP handshake response \n " ) ;
2012-05-24 13:48:42 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2012-07-19 14:13:58 +02:00
av_log ( s , AV_LOG_DEBUG , " Type answer %d \n " , serverdata [ 0 ] ) ;
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Server version %d.%d.%d.%d \n " ,
2009-07-31 08:49:36 +02:00
serverdata [ 5 ] , serverdata [ 6 ] , serverdata [ 7 ] , serverdata [ 8 ] ) ;
2010-01-12 07:44:49 +01:00
if ( rt - > is_input & & serverdata [ 5 ] > = 3 ) {
2009-12-04 17:52:42 +01:00
server_pos = rtmp_validate_digest ( serverdata + 1 , 772 ) ;
2012-05-23 18:55:34 +02:00
if ( server_pos < 0 )
return server_pos ;
2009-07-31 08:49:36 +02:00
if ( ! server_pos ) {
2012-07-19 14:13:58 +02:00
type = 1 ;
2009-12-04 17:52:42 +01:00
server_pos = rtmp_validate_digest ( serverdata + 1 , 8 ) ;
2012-05-23 18:55:34 +02:00
if ( server_pos < 0 )
return server_pos ;
2009-12-04 17:52:42 +01:00
if ( ! server_pos ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_ERROR , " Server response validating failed \n " ) ;
2012-05-23 18:55:52 +02:00
return AVERROR ( EIO ) ;
2009-12-04 17:52:42 +01:00
}
2009-07-31 08:49:36 +02:00
}
2012-07-05 13:05:46 +02:00
ret = ff_rtmp_calc_digest ( tosend + 1 + client_pos , 32 , 0 ,
rtmp_server_key , sizeof ( rtmp_server_key ) ,
digest ) ;
2012-05-23 18:55:34 +02:00
if ( ret < 0 )
return ret ;
2012-07-05 13:05:46 +02:00
ret = ff_rtmp_calc_digest ( clientdata , RTMP_HANDSHAKE_PACKET_SIZE - 32 ,
2012-07-19 14:13:58 +02:00
0 , digest , 32 , signature ) ;
2012-05-23 18:55:34 +02:00
if ( ret < 0 )
return ret ;
2012-07-24 13:46:28 +02:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 14:13:58 +02:00
/* Compute the shared secret key sent by the server and initialize
* the RC4 encryption . */
if ( ( ret = ff_rtmpe_compute_secret_key ( rt - > stream , serverdata + 1 ,
tosend + 1 , type ) ) < 0 )
return ret ;
/* Encrypt the signature received by the server. */
ff_rtmpe_encrypt_sig ( rt - > stream , signature , digest , serverdata [ 0 ] ) ;
}
if ( memcmp ( signature , clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32 , 32 ) ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_ERROR , " Signature mismatch \n " ) ;
2012-05-23 18:55:52 +02:00
return AVERROR ( EIO ) ;
2009-12-04 17:52:42 +01:00
}
2009-07-31 08:49:36 +02:00
2009-12-04 17:52:42 +01:00
for ( i = 0 ; i < RTMP_HANDSHAKE_PACKET_SIZE ; i + + )
tosend [ i ] = av_lfg_get ( & rnd ) > > 24 ;
2012-07-05 13:05:46 +02:00
ret = ff_rtmp_calc_digest ( serverdata + 1 + server_pos , 32 , 0 ,
rtmp_player_key , sizeof ( rtmp_player_key ) ,
digest ) ;
2012-05-23 18:55:34 +02:00
if ( ret < 0 )
return ret ;
2012-07-05 13:05:46 +02:00
ret = ff_rtmp_calc_digest ( tosend , RTMP_HANDSHAKE_PACKET_SIZE - 32 , 0 ,
digest , 32 ,
tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32 ) ;
2012-05-23 18:55:34 +02:00
if ( ret < 0 )
return ret ;
2009-12-04 17:52:42 +01:00
2012-07-24 13:46:28 +02:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 14:13:58 +02:00
/* Encrypt the signature to be send to the server. */
ff_rtmpe_encrypt_sig ( rt - > stream , tosend +
RTMP_HANDSHAKE_PACKET_SIZE - 32 , digest ,
serverdata [ 0 ] ) ;
}
2009-12-04 17:52:42 +01:00
// write reply back to the server
2012-05-24 13:48:25 +02:00
if ( ( ret = ffurl_write ( rt - > stream , tosend ,
RTMP_HANDSHAKE_PACKET_SIZE ) ) < 0 )
return ret ;
2012-07-19 14:13:58 +02:00
2012-07-24 13:46:28 +02:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 14:13:58 +02:00
/* Set RC4 keys for encryption and update the keystreams. */
if ( ( ret = ff_rtmpe_update_keystream ( rt - > stream ) ) < 0 )
return ret ;
}
2009-12-04 17:52:16 +01:00
} else {
2012-07-24 13:46:28 +02:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 14:13:58 +02:00
/* Compute the shared secret key sent by the server and initialize
* the RC4 encryption . */
if ( ( ret = ff_rtmpe_compute_secret_key ( rt - > stream , serverdata + 1 ,
tosend + 1 , 1 ) ) < 0 )
return ret ;
if ( serverdata [ 0 ] = = 9 ) {
/* Encrypt the signature received by the server. */
ff_rtmpe_encrypt_sig ( rt - > stream , signature , digest ,
serverdata [ 0 ] ) ;
}
}
2012-05-24 13:48:25 +02:00
if ( ( ret = ffurl_write ( rt - > stream , serverdata + 1 ,
RTMP_HANDSHAKE_PACKET_SIZE ) ) < 0 )
return ret ;
2012-07-19 14:13:58 +02:00
2012-07-24 13:46:28 +02:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 14:13:58 +02:00
/* Set RC4 keys for encryption and update the keystreams. */
if ( ( ret = ff_rtmpe_update_keystream ( rt - > stream ) ) < 0 )
return ret ;
}
2009-12-04 17:52:16 +01:00
}
2009-07-31 08:49:36 +02:00
return 0 ;
}
2012-07-21 12:59:52 +02:00
static int handle_chunk_size ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
int ret ;
2012-07-26 14:05:58 +02:00
if ( pkt - > data_size < 4 ) {
2012-07-21 12:59:52 +02:00
av_log ( s , AV_LOG_ERROR ,
2012-07-26 14:05:58 +02:00
" Too short chunk size change packet (%d) \n " ,
2012-07-21 12:59:52 +02:00
pkt - > data_size ) ;
2012-07-25 20:51:08 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:52 +02:00
}
if ( ! rt - > is_input ) {
if ( ( ret = ff_rtmp_packet_write ( rt - > stream , pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ) < 0 )
return ret ;
}
rt - > chunk_size = AV_RB32 ( pkt - > data ) ;
if ( rt - > chunk_size < = 0 ) {
av_log ( s , AV_LOG_ERROR , " Incorrect chunk size %d \n " , rt - > chunk_size ) ;
2012-07-25 20:51:08 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:52 +02:00
}
av_log ( s , AV_LOG_DEBUG , " New chunk size = %d \n " , rt - > chunk_size ) ;
return 0 ;
}
2012-07-21 12:59:51 +02:00
static int handle_ping ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
int t , ret ;
t = AV_RB16 ( pkt - > data ) ;
if ( t = = 6 ) {
if ( ( ret = gen_pong ( s , rt , pkt ) ) < 0 )
return ret ;
}
return 0 ;
}
2012-07-21 12:59:50 +02:00
static int handle_client_bw ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
if ( pkt - > data_size < 4 ) {
av_log ( s , AV_LOG_ERROR ,
" Client bandwidth report packet is less than 4 bytes long (%d) \n " ,
pkt - > data_size ) ;
2012-07-25 20:51:09 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:50 +02:00
}
2012-07-25 20:51:11 +02:00
rt - > client_report_size = AV_RB32 ( pkt - > data ) ;
if ( rt - > client_report_size < = 0 ) {
av_log ( s , AV_LOG_ERROR , " Incorrect client bandwidth %d \n " ,
rt - > client_report_size ) ;
return AVERROR_INVALIDDATA ;
}
av_log ( s , AV_LOG_DEBUG , " Client bandwidth = %d \n " , rt - > client_report_size ) ;
rt - > client_report_size > > = 1 ;
2012-07-21 12:59:50 +02:00
return 0 ;
}
2012-07-21 12:59:49 +02:00
static int handle_server_bw ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
2012-07-26 14:05:18 +02:00
if ( pkt - > data_size < 4 ) {
av_log ( s , AV_LOG_ERROR ,
" Too short server bandwidth report packet (%d) \n " ,
pkt - > data_size ) ;
return AVERROR_INVALIDDATA ;
}
2012-07-21 12:59:49 +02:00
rt - > server_bw = AV_RB32 ( pkt - > data ) ;
if ( rt - > server_bw < = 0 ) {
av_log ( s , AV_LOG_ERROR , " Incorrect server bandwidth %d \n " ,
rt - > server_bw ) ;
2012-07-25 20:51:10 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:49 +02:00
}
av_log ( s , AV_LOG_DEBUG , " Server bandwidth = %d \n " , rt - > server_bw ) ;
return 0 ;
}
2012-07-21 12:59:58 +02:00
static int handle_invoke ( URLContext * s , RTMPPacket * pkt )
2009-07-31 08:49:36 +02:00
{
2012-07-21 12:59:58 +02:00
RTMPContext * rt = s - > priv_data ;
2009-07-31 08:49:36 +02:00
int i , t ;
const uint8_t * data_end = pkt - > data + pkt - > data_size ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-07-31 08:49:36 +02:00
2012-07-21 12:59:58 +02:00
//TODO: check for the messages sent for wrong state?
if ( ! memcmp ( pkt - > data , " \002 \000 \006 _error " , 9 ) ) {
uint8_t tmpstr [ 256 ] ;
2009-12-11 18:13:35 +01:00
2012-07-21 12:59:58 +02:00
if ( ! ff_amf_get_field_value ( pkt - > data + 9 , data_end ,
" description " , tmpstr , sizeof ( tmpstr ) ) )
av_log ( s , AV_LOG_ERROR , " Server error: %s \n " , tmpstr ) ;
return - 1 ;
} else if ( ! memcmp ( pkt - > data , " \002 \000 \007 _result " , 10 ) ) {
switch ( rt - > state ) {
2009-07-31 08:49:36 +02:00
case STATE_HANDSHAKED :
2009-12-04 17:52:16 +01:00
if ( ! rt - > is_input ) {
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_release_stream ( s , rt ) ) < 0 )
return ret ;
if ( ( ret = gen_fcpublish_stream ( s , rt ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
rt - > state = STATE_RELEASING ;
} else {
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_server_bw ( s , rt ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
rt - > state = STATE_CONNECTING ;
}
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_create_stream ( s , rt ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
break ;
case STATE_FCPUBLISH :
2009-07-31 08:49:36 +02:00
rt - > state = STATE_CONNECTING ;
break ;
2009-12-04 17:52:16 +01:00
case STATE_RELEASING :
rt - > state = STATE_FCPUBLISH ;
/* hack for Wowza Media Server, it does not send result for
* releaseStream and FCPublish calls */
if ( ! pkt - > data [ 10 ] ) {
2011-11-27 15:04:16 +01:00
int pkt_id = av_int2double ( AV_RB64 ( pkt - > data + 11 ) ) ;
2011-12-05 11:35:06 +01:00
if ( pkt_id = = rt - > create_stream_invoke )
2009-12-04 17:52:16 +01:00
rt - > state = STATE_CONNECTING ;
}
2009-12-06 08:03:46 +01:00
if ( rt - > state ! = STATE_CONNECTING )
2009-12-04 17:52:16 +01:00
break ;
2009-07-31 08:49:36 +02:00
case STATE_CONNECTING :
//extract a number from the result
if ( pkt - > data [ 10 ] | | pkt - > data [ 19 ] ! = 5 | | pkt - > data [ 20 ] ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_WARNING , " Unexpected reply on connect() \n " ) ;
2009-07-31 08:49:36 +02:00
} else {
2011-11-27 15:04:16 +01:00
rt - > main_channel_id = av_int2double ( AV_RB64 ( pkt - > data + 21 ) ) ;
2009-07-31 08:49:36 +02:00
}
2009-12-04 17:52:16 +01:00
if ( rt - > is_input ) {
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_play ( s , rt ) ) < 0 )
return ret ;
2012-06-13 14:48:02 +02:00
if ( ( ret = gen_buffer_time ( s , rt ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
} else {
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_publish ( s , rt ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
}
2009-07-31 08:49:36 +02:00
rt - > state = STATE_READY ;
break ;
}
2012-07-21 12:59:58 +02:00
} else if ( ! memcmp ( pkt - > data , " \002 \000 \010 onStatus " , 11 ) ) {
const uint8_t * ptr = pkt - > data + 11 ;
uint8_t tmpstr [ 256 ] ;
for ( i = 0 ; i < 2 ; i + + ) {
t = ff_amf_tag_size ( ptr , data_end ) ;
if ( t < 0 )
return 1 ;
ptr + = t ;
}
t = ff_amf_get_field_value ( ptr , data_end ,
" level " , tmpstr , sizeof ( tmpstr ) ) ;
if ( ! t & & ! strcmp ( tmpstr , " error " ) ) {
if ( ! ff_amf_get_field_value ( ptr , data_end ,
" description " , tmpstr , sizeof ( tmpstr ) ) )
av_log ( s , AV_LOG_ERROR , " Server error: %s \n " , tmpstr ) ;
return - 1 ;
}
t = ff_amf_get_field_value ( ptr , data_end ,
" code " , tmpstr , sizeof ( tmpstr ) ) ;
if ( ! t & & ! strcmp ( tmpstr , " NetStream.Play.Start " ) ) rt - > state = STATE_PLAYING ;
if ( ! t & & ! strcmp ( tmpstr , " NetStream.Play.Stop " ) ) rt - > state = STATE_STOPPED ;
if ( ! t & & ! strcmp ( tmpstr , " NetStream.Play.UnpublishNotify " ) ) rt - > state = STATE_STOPPED ;
if ( ! t & & ! strcmp ( tmpstr , " NetStream.Publish.Start " ) ) rt - > state = STATE_PUBLISHING ;
} else if ( ! memcmp ( pkt - > data , " \002 \000 \010 onBWDone " , 11 ) ) {
if ( ( ret = gen_check_bw ( s , rt ) ) < 0 )
return ret ;
}
return 0 ;
}
/**
* Parse received packet and possibly perform some action depending on
* the packet contents .
* @ return 0 for no errors , negative values for serious errors which prevent
* further communications , positive values for uncritical errors
*/
static int rtmp_parse_result ( URLContext * s , RTMPContext * rt , RTMPPacket * pkt )
{
int ret ;
# ifdef DEBUG
ff_rtmp_packet_dump ( s , pkt ) ;
# endif
switch ( pkt - > type ) {
case RTMP_PT_CHUNK_SIZE :
if ( ( ret = handle_chunk_size ( s , pkt ) ) < 0 )
return ret ;
break ;
case RTMP_PT_PING :
if ( ( ret = handle_ping ( s , pkt ) ) < 0 )
return ret ;
break ;
case RTMP_PT_CLIENT_BW :
if ( ( ret = handle_client_bw ( s , pkt ) ) < 0 )
return ret ;
break ;
case RTMP_PT_SERVER_BW :
if ( ( ret = handle_server_bw ( s , pkt ) ) < 0 )
return ret ;
break ;
case RTMP_PT_INVOKE :
if ( ( ret = handle_invoke ( s , pkt ) ) < 0 )
return ret ;
2009-07-31 08:49:36 +02:00
break ;
2012-07-03 20:20:02 +02:00
case RTMP_PT_VIDEO :
case RTMP_PT_AUDIO :
/* Audio and Video packets are parsed in get_packet() */
break ;
2012-06-13 14:45:57 +02:00
default :
av_log ( s , AV_LOG_VERBOSE , " Unknown packet type received 0x%02X \n " , pkt - > type ) ;
break ;
2009-07-31 08:49:36 +02:00
}
return 0 ;
}
/**
2010-06-30 17:38:06 +02:00
* Interact with the server by receiving and sending RTMP packets until
2009-07-31 08:49:36 +02:00
* there is some significant data ( media data or expected status notification ) .
*
* @ param s reading context
2009-11-13 00:05:56 +01:00
* @ param for_header non - zero value tells function to work until it
* gets notification from the server that playing has been started ,
* otherwise function will work until some media data is received ( or
* an error happens )
2009-07-31 08:49:36 +02:00
* @ return 0 for successful operation , negative value in case of error
*/
static int get_packet ( URLContext * s , int for_header )
{
RTMPContext * rt = s - > priv_data ;
int ret ;
2010-01-12 08:10:47 +01:00
uint8_t * p ;
const uint8_t * next ;
uint32_t data_size ;
uint32_t ts , cts , pts = 0 ;
2009-07-31 08:49:36 +02:00
2009-12-11 12:37:21 +01:00
if ( rt - > state = = STATE_STOPPED )
return AVERROR_EOF ;
2009-12-06 08:03:46 +01:00
for ( ; ; ) {
2011-05-25 18:08:29 +02:00
RTMPPacket rpkt = { 0 } ;
2009-07-31 08:49:36 +02:00
if ( ( ret = ff_rtmp_packet_read ( rt - > stream , & rpkt ,
2010-01-30 10:47:57 +01:00
rt - > chunk_size , rt - > prev_pkt [ 0 ] ) ) < = 0 ) {
2010-01-30 10:45:52 +01:00
if ( ret = = 0 ) {
2009-07-31 08:49:36 +02:00
return AVERROR ( EAGAIN ) ;
} else {
return AVERROR ( EIO ) ;
}
}
2010-02-18 17:27:18 +01:00
rt - > bytes_read + = ret ;
if ( rt - > bytes_read > rt - > last_bytes_read + rt - > client_report_size ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Sending bytes read report \n " ) ;
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_bytes_read ( s , rt , rpkt . timestamp + 1 ) ) < 0 )
return ret ;
2010-02-18 17:27:18 +01:00
rt - > last_bytes_read = rt - > bytes_read ;
}
2009-07-31 08:49:36 +02:00
ret = rtmp_parse_result ( s , rt , & rpkt ) ;
if ( ret < 0 ) { //serious error in current packet
ff_rtmp_packet_destroy ( & rpkt ) ;
2012-05-23 22:45:03 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
2009-12-11 12:37:21 +01:00
if ( rt - > state = = STATE_STOPPED ) {
ff_rtmp_packet_destroy ( & rpkt ) ;
return AVERROR_EOF ;
}
2009-12-04 17:52:16 +01:00
if ( for_header & & ( rt - > state = = STATE_PLAYING | | rt - > state = = STATE_PUBLISHING ) ) {
2009-07-31 08:49:36 +02:00
ff_rtmp_packet_destroy ( & rpkt ) ;
return 0 ;
}
2009-12-04 17:52:16 +01:00
if ( ! rpkt . data_size | | ! rt - > is_input ) {
2009-07-31 08:49:36 +02:00
ff_rtmp_packet_destroy ( & rpkt ) ;
continue ;
}
if ( rpkt . type = = RTMP_PT_VIDEO | | rpkt . type = = RTMP_PT_AUDIO | |
2009-11-22 09:40:55 +01:00
( rpkt . type = = RTMP_PT_NOTIFY & & ! memcmp ( " \002 \000 \012 onMetaData " , rpkt . data , 13 ) ) ) {
2010-01-12 08:10:47 +01:00
ts = rpkt . timestamp ;
2009-07-31 08:49:36 +02:00
// generate packet header and put data into buffer for FLV demuxer
rt - > flv_off = 0 ;
rt - > flv_size = rpkt . data_size + 15 ;
rt - > flv_data = p = av_realloc ( rt - > flv_data , rt - > flv_size ) ;
bytestream_put_byte ( & p , rpkt . type ) ;
bytestream_put_be24 ( & p , rpkt . data_size ) ;
bytestream_put_be24 ( & p , ts ) ;
bytestream_put_byte ( & p , ts > > 24 ) ;
bytestream_put_be24 ( & p , 0 ) ;
bytestream_put_buffer ( & p , rpkt . data , rpkt . data_size ) ;
bytestream_put_be32 ( & p , 0 ) ;
ff_rtmp_packet_destroy ( & rpkt ) ;
return 0 ;
} else if ( rpkt . type = = RTMP_PT_METADATA ) {
// we got raw FLV data, make it available for FLV demuxer
rt - > flv_off = 0 ;
rt - > flv_size = rpkt . data_size ;
rt - > flv_data = av_realloc ( rt - > flv_data , rt - > flv_size ) ;
2010-01-12 08:10:47 +01:00
/* rewrite timestamps */
next = rpkt . data ;
ts = rpkt . timestamp ;
while ( next - rpkt . data < rpkt . data_size - 11 ) {
next + + ;
data_size = bytestream_get_be24 ( & next ) ;
p = next ;
cts = bytestream_get_be24 ( & next ) ;
2010-05-25 09:01:04 +02:00
cts | = bytestream_get_byte ( & next ) < < 24 ;
2010-01-12 08:10:47 +01:00
if ( pts = = 0 )
pts = cts ;
ts + = cts - pts ;
pts = cts ;
bytestream_put_be24 ( & p , ts ) ;
bytestream_put_byte ( & p , ts > > 24 ) ;
next + = data_size + 3 + 4 ;
}
2009-07-31 08:49:36 +02:00
memcpy ( rt - > flv_data , rpkt . data , rpkt . data_size ) ;
ff_rtmp_packet_destroy ( & rpkt ) ;
return 0 ;
}
ff_rtmp_packet_destroy ( & rpkt ) ;
}
}
static int rtmp_close ( URLContext * h )
{
RTMPContext * rt = h - > priv_data ;
2012-05-23 22:45:03 +02:00
int ret = 0 ;
2009-07-31 08:49:36 +02:00
2009-12-06 08:03:46 +01:00
if ( ! rt - > is_input ) {
2009-12-04 17:52:16 +01:00
rt - > flv_data = NULL ;
if ( rt - > out_pkt . data_size )
ff_rtmp_packet_destroy ( & rt - > out_pkt ) ;
2009-12-06 08:01:37 +01:00
if ( rt - > state > STATE_FCPUBLISH )
2012-05-23 22:45:03 +02:00
ret = gen_fcunpublish_stream ( h , rt ) ;
2009-12-04 17:52:16 +01:00
}
2009-12-06 08:01:37 +01:00
if ( rt - > state > STATE_HANDSHAKED )
2012-05-23 22:45:03 +02:00
ret = gen_delete_stream ( h , rt ) ;
2009-12-04 17:52:16 +01:00
2009-07-31 08:49:36 +02:00
av_freep ( & rt - > flv_data ) ;
2011-03-31 17:36:06 +02:00
ffurl_close ( rt - > stream ) ;
2012-05-23 22:45:03 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
/**
2010-06-30 17:38:06 +02:00
* Open RTMP connection and verify that the stream can be played .
2009-07-31 08:49:36 +02:00
*
* URL syntax : rtmp : //server[:port][/app][/playpath]
* where ' app ' is first one or two directories in the path
* ( e . g . / ondemand / , / flash / live / , etc . )
* and ' playpath ' is a file name ( the rest of the path ,
* may be prefixed with " mp4: " )
*/
static int rtmp_open ( URLContext * s , const char * uri , int flags )
{
2011-12-01 10:44:21 +01:00
RTMPContext * rt = s - > priv_data ;
2009-12-03 16:47:00 +01:00
char proto [ 8 ] , hostname [ 256 ] , path [ 1024 ] , * fname ;
2012-04-15 21:49:48 +02:00
char * old_app ;
2009-07-31 08:49:36 +02:00
uint8_t buf [ 2048 ] ;
2009-12-02 13:55:10 +01:00
int port ;
2012-07-17 12:02:43 +02:00
AVDictionary * opts = NULL ;
2009-07-31 08:49:36 +02:00
int ret ;
2011-04-15 16:42:09 +02:00
rt - > is_input = ! ( flags & AVIO_FLAG_WRITE ) ;
2009-07-31 08:49:36 +02:00
2010-06-27 16:16:46 +02:00
av_url_split ( proto , sizeof ( proto ) , NULL , 0 , hostname , sizeof ( hostname ) , & port ,
2010-03-08 10:05:03 +01:00
path , sizeof ( path ) , s - > filename ) ;
2009-07-31 08:49:36 +02:00
2012-07-17 12:02:43 +02:00
if ( ! strcmp ( proto , " rtmpt " ) | | ! strcmp ( proto , " rtmpts " ) ) {
if ( ! strcmp ( proto , " rtmpts " ) )
av_dict_set ( & opts , " ffrtmphttp_tls " , " 1 " , 1 ) ;
2012-06-17 20:24:43 +02:00
/* open the http tunneling connection */
2012-07-16 11:56:20 +02:00
ff_url_join ( buf , sizeof ( buf ) , " ffrtmphttp " , NULL , hostname , port , NULL ) ;
2012-07-17 12:02:42 +02:00
} else if ( ! strcmp ( proto , " rtmps " ) ) {
/* open the tls connection */
if ( port < 0 )
port = RTMPS_DEFAULT_PORT ;
ff_url_join ( buf , sizeof ( buf ) , " tls " , NULL , hostname , port , NULL ) ;
2012-07-20 16:36:47 +02:00
} else if ( ! strcmp ( proto , " rtmpe " ) | | ( ! strcmp ( proto , " rtmpte " ) ) ) {
if ( ! strcmp ( proto , " rtmpte " ) )
av_dict_set ( & opts , " ffrtmpcrypt_tunneling " , " 1 " , 1 ) ;
2012-07-19 14:13:58 +02:00
/* open the encrypted connection */
ff_url_join ( buf , sizeof ( buf ) , " ffrtmpcrypt " , NULL , hostname , port , NULL ) ;
rt - > encrypted = 1 ;
2012-06-17 20:24:43 +02:00
} else {
/* open the tcp connection */
if ( port < 0 )
port = RTMP_DEFAULT_PORT ;
ff_url_join ( buf , sizeof ( buf ) , " tcp " , NULL , hostname , port , NULL ) ;
}
2009-07-31 08:49:36 +02:00
2012-05-23 22:45:03 +02:00
if ( ( ret = ffurl_open ( & rt - > stream , buf , AVIO_FLAG_READ_WRITE ,
2012-07-17 12:02:43 +02:00
& s - > interrupt_callback , & opts ) ) < 0 ) {
2011-04-15 16:42:09 +02:00
av_log ( s , AV_LOG_ERROR , " Cannot open connection %s \n " , buf ) ;
2009-07-31 08:49:36 +02:00
goto fail ;
2009-11-22 09:42:55 +01:00
}
2009-07-31 08:49:36 +02:00
2009-12-04 17:52:42 +01:00
rt - > state = STATE_START ;
2012-05-23 22:45:03 +02:00
if ( ( ret = rtmp_handshake ( s , rt ) ) < 0 )
2011-12-01 10:34:06 +01:00
goto fail ;
2009-07-31 08:49:36 +02:00
2009-12-04 17:52:42 +01:00
rt - > chunk_size = 128 ;
rt - > state = STATE_HANDSHAKED ;
2012-04-15 21:49:48 +02:00
// Keep the application name when it has been defined by the user.
old_app = rt - > app ;
rt - > app = av_malloc ( APP_MAX_LENGTH ) ;
if ( ! rt - > app ) {
2012-05-23 22:45:03 +02:00
ret = AVERROR ( ENOMEM ) ;
goto fail ;
2012-04-15 21:49:48 +02:00
}
2009-12-04 17:52:42 +01:00
//extract "app" part from path
if ( ! strncmp ( path , " /ondemand/ " , 10 ) ) {
fname = path + 10 ;
memcpy ( rt - > app , " ondemand " , 9 ) ;
} else {
2012-05-16 10:45:47 +02:00
char * next = * path ? path + 1 : path ;
char * p = strchr ( next , ' / ' ) ;
2009-12-04 17:52:42 +01:00
if ( ! p ) {
2012-05-16 10:45:47 +02:00
fname = next ;
2009-12-04 17:52:42 +01:00
rt - > app [ 0 ] = ' \0 ' ;
2009-07-31 08:49:36 +02:00
} else {
2012-05-15 02:24:27 +02:00
// make sure we do not mismatch a playpath for an application instance
2009-12-04 17:52:42 +01:00
char * c = strchr ( p + 1 , ' : ' ) ;
fname = strchr ( p + 1 , ' / ' ) ;
2012-05-15 02:24:27 +02:00
if ( ! fname | | ( c & & c < fname ) ) {
2009-12-04 17:52:42 +01:00
fname = p + 1 ;
av_strlcpy ( rt - > app , path + 1 , p - path ) ;
2009-07-31 08:49:36 +02:00
} else {
2009-12-04 17:52:42 +01:00
fname + + ;
av_strlcpy ( rt - > app , path + 1 , fname - path - 1 ) ;
2009-07-31 08:49:36 +02:00
}
}
2009-12-04 17:52:42 +01:00
}
2012-04-15 21:49:48 +02:00
if ( old_app ) {
// The name of application has been defined by the user, override it.
av_free ( rt - > app ) ;
rt - > app = old_app ;
}
2012-04-15 21:50:50 +02:00
if ( ! rt - > playpath ) {
2012-06-07 17:46:34 +02:00
int len = strlen ( fname ) ;
2012-04-15 21:50:50 +02:00
rt - > playpath = av_malloc ( PLAYPATH_MAX_LENGTH ) ;
if ( ! rt - > playpath ) {
2012-05-23 22:45:03 +02:00
ret = AVERROR ( ENOMEM ) ;
goto fail ;
2012-04-15 21:50:50 +02:00
}
2012-06-11 14:21:32 +02:00
if ( ! strchr ( fname , ' : ' ) & & len > = 4 & &
2012-06-07 17:46:34 +02:00
( ! strcmp ( fname + len - 4 , " .f4v " ) | |
! strcmp ( fname + len - 4 , " .mp4 " ) ) ) {
2012-04-15 21:50:50 +02:00
memcpy ( rt - > playpath , " mp4: " , 5 ) ;
2012-06-11 14:21:32 +02:00
} else if ( len > = 4 & & ! strcmp ( fname + len - 4 , " .flv " ) ) {
2012-06-07 17:46:34 +02:00
fname [ len - 4 ] = ' \0 ' ;
2012-04-15 21:50:50 +02:00
} else {
rt - > playpath [ 0 ] = 0 ;
}
strncat ( rt - > playpath , fname , PLAYPATH_MAX_LENGTH - 5 ) ;
2009-12-04 17:52:42 +01:00
}
2009-07-31 08:49:36 +02:00
2012-05-09 02:12:14 +02:00
if ( ! rt - > tcurl ) {
rt - > tcurl = av_malloc ( TCURL_MAX_LENGTH ) ;
2012-05-23 18:55:34 +02:00
if ( ! rt - > tcurl ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2012-05-09 02:12:14 +02:00
ff_url_join ( rt - > tcurl , TCURL_MAX_LENGTH , proto , NULL , hostname ,
port , " /%s " , rt - > app ) ;
}
2012-05-09 02:12:15 +02:00
if ( ! rt - > flashver ) {
rt - > flashver = av_malloc ( FLASHVER_MAX_LENGTH ) ;
2012-05-23 18:55:34 +02:00
if ( ! rt - > flashver ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2012-05-09 02:12:15 +02:00
if ( rt - > is_input ) {
snprintf ( rt - > flashver , FLASHVER_MAX_LENGTH , " %s %d,%d,%d,%d " ,
RTMP_CLIENT_PLATFORM , RTMP_CLIENT_VER1 , RTMP_CLIENT_VER2 ,
RTMP_CLIENT_VER3 , RTMP_CLIENT_VER4 ) ;
} else {
snprintf ( rt - > flashver , FLASHVER_MAX_LENGTH ,
" FMLE/3.0 (compatible; %s) " , LIBAVFORMAT_IDENT ) ;
}
}
2010-02-18 17:27:18 +01:00
rt - > client_report_size = 1048576 ;
rt - > bytes_read = 0 ;
rt - > last_bytes_read = 0 ;
2012-06-13 14:47:26 +02:00
rt - > server_bw = 2500000 ;
2010-02-18 17:27:18 +01:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , " Proto = %s, path = %s, app = %s, fname = %s \n " ,
2009-12-04 17:52:42 +01:00
proto , path , rt - > app , rt - > playpath ) ;
2012-05-23 22:45:03 +02:00
if ( ( ret = gen_connect ( s , rt ) ) < 0 )
goto fail ;
2009-07-31 08:49:36 +02:00
2009-12-04 17:52:42 +01:00
do {
ret = get_packet ( s , 1 ) ;
} while ( ret = = EAGAIN ) ;
if ( ret < 0 )
goto fail ;
2009-12-04 17:52:16 +01:00
if ( rt - > is_input ) {
2009-07-31 08:49:36 +02:00
// generate FLV header for demuxer
rt - > flv_size = 13 ;
rt - > flv_data = av_realloc ( rt - > flv_data , rt - > flv_size ) ;
rt - > flv_off = 0 ;
memcpy ( rt - > flv_data , " FLV \1 \5 \0 \0 \0 \011 \0 \0 \0 \0 " , rt - > flv_size ) ;
2009-12-04 17:52:16 +01:00
} else {
rt - > flv_size = 0 ;
rt - > flv_data = NULL ;
rt - > flv_off = 0 ;
2011-09-21 22:21:30 +02:00
rt - > skip_bytes = 13 ;
2009-07-31 08:49:36 +02:00
}
2011-03-31 17:58:04 +02:00
s - > max_packet_size = rt - > stream - > max_packet_size ;
2009-07-31 08:49:36 +02:00
s - > is_streamed = 1 ;
return 0 ;
fail :
2012-07-17 12:02:43 +02:00
av_dict_free ( & opts ) ;
2009-07-31 08:49:36 +02:00
rtmp_close ( s ) ;
2012-05-23 22:45:03 +02:00
return ret ;
2009-07-31 08:49:36 +02:00
}
static int rtmp_read ( URLContext * s , uint8_t * buf , int size )
{
RTMPContext * rt = s - > priv_data ;
int orig_size = size ;
int ret ;
while ( size > 0 ) {
int data_left = rt - > flv_size - rt - > flv_off ;
if ( data_left > = size ) {
memcpy ( buf , rt - > flv_data + rt - > flv_off , size ) ;
rt - > flv_off + = size ;
return orig_size ;
}
if ( data_left > 0 ) {
memcpy ( buf , rt - > flv_data + rt - > flv_off , data_left ) ;
buf + = data_left ;
size - = data_left ;
rt - > flv_off = rt - > flv_size ;
2010-06-18 14:02:51 +02:00
return data_left ;
2009-07-31 08:49:36 +02:00
}
if ( ( ret = get_packet ( s , 0 ) ) < 0 )
return ret ;
}
return orig_size ;
}
2011-02-03 12:17:35 +01:00
static int rtmp_write ( URLContext * s , const uint8_t * buf , int size )
2009-07-31 08:49:36 +02:00
{
2011-02-03 12:17:35 +01:00
RTMPContext * rt = s - > priv_data ;
2009-12-04 17:52:16 +01:00
int size_temp = size ;
int pktsize , pkttype ;
uint32_t ts ;
const uint8_t * buf_temp = buf ;
2012-06-14 15:28:40 +02:00
uint8_t c ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 17:52:16 +01:00
do {
2011-09-21 22:21:30 +02:00
if ( rt - > skip_bytes ) {
int skip = FFMIN ( rt - > skip_bytes , size_temp ) ;
buf_temp + = skip ;
size_temp - = skip ;
rt - > skip_bytes - = skip ;
continue ;
}
if ( rt - > flv_header_bytes < 11 ) {
const uint8_t * header = rt - > flv_header ;
int copy = FFMIN ( 11 - rt - > flv_header_bytes , size_temp ) ;
bytestream_get_buffer ( & buf_temp , rt - > flv_header + rt - > flv_header_bytes , copy ) ;
rt - > flv_header_bytes + = copy ;
size_temp - = copy ;
if ( rt - > flv_header_bytes < 11 )
break ;
2009-12-04 17:52:16 +01:00
2011-09-21 22:21:30 +02:00
pkttype = bytestream_get_byte ( & header ) ;
pktsize = bytestream_get_be24 ( & header ) ;
ts = bytestream_get_be24 ( & header ) ;
ts | = bytestream_get_byte ( & header ) < < 24 ;
bytestream_get_be24 ( & header ) ;
2009-12-04 17:52:16 +01:00
rt - > flv_size = pktsize ;
//force 12bytes header
if ( ( ( pkttype = = RTMP_PT_VIDEO | | pkttype = = RTMP_PT_AUDIO ) & & ts = = 0 ) | |
pkttype = = RTMP_PT_NOTIFY ) {
if ( pkttype = = RTMP_PT_NOTIFY )
pktsize + = 16 ;
rt - > prev_pkt [ 1 ] [ RTMP_SOURCE_CHANNEL ] . channel_id = 0 ;
}
//this can be a big packet, it's better to send it right here
2012-05-23 22:45:03 +02:00
if ( ( ret = ff_rtmp_packet_create ( & rt - > out_pkt , RTMP_SOURCE_CHANNEL ,
pkttype , ts , pktsize ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
rt - > out_pkt . extra = rt - > main_channel_id ;
rt - > flv_data = rt - > out_pkt . data ;
if ( pkttype = = RTMP_PT_NOTIFY )
ff_amf_write_string ( & rt - > flv_data , " @setDataFrame " ) ;
}
if ( rt - > flv_size - rt - > flv_off > size_temp ) {
bytestream_get_buffer ( & buf_temp , rt - > flv_data + rt - > flv_off , size_temp ) ;
rt - > flv_off + = size_temp ;
2011-09-18 05:39:53 +02:00
size_temp = 0 ;
2009-12-04 17:52:16 +01:00
} else {
bytestream_get_buffer ( & buf_temp , rt - > flv_data + rt - > flv_off , rt - > flv_size - rt - > flv_off ) ;
2011-09-18 05:39:53 +02:00
size_temp - = rt - > flv_size - rt - > flv_off ;
2009-12-04 17:52:16 +01:00
rt - > flv_off + = rt - > flv_size - rt - > flv_off ;
}
if ( rt - > flv_off = = rt - > flv_size ) {
2011-09-21 22:21:30 +02:00
rt - > skip_bytes = 4 ;
2012-05-24 13:48:25 +02:00
if ( ( ret = ff_rtmp_packet_write ( rt - > stream , & rt - > out_pkt ,
rt - > chunk_size , rt - > prev_pkt [ 1 ] ) ) < 0 )
return ret ;
2009-12-04 17:52:16 +01:00
ff_rtmp_packet_destroy ( & rt - > out_pkt ) ;
rt - > flv_size = 0 ;
rt - > flv_off = 0 ;
2011-09-21 22:21:30 +02:00
rt - > flv_header_bytes = 0 ;
2012-06-18 14:55:55 +02:00
rt - > flv_nb_packets + + ;
2009-12-04 17:52:16 +01:00
}
2011-09-18 05:39:53 +02:00
} while ( buf_temp - buf < size ) ;
2012-06-14 15:28:40 +02:00
2012-06-18 14:55:55 +02:00
if ( rt - > flv_nb_packets < rt - > flush_interval )
return size ;
rt - > flv_nb_packets = 0 ;
2012-06-14 15:28:40 +02:00
/* set stream into nonblocking mode */
rt - > stream - > flags | = AVIO_FLAG_NONBLOCK ;
/* try to read one byte from the stream */
ret = ffurl_read ( rt - > stream , & c , 1 ) ;
/* switch the stream back into blocking mode */
rt - > stream - > flags & = ~ AVIO_FLAG_NONBLOCK ;
if ( ret = = AVERROR ( EAGAIN ) ) {
/* no incoming data to handle */
return size ;
} else if ( ret < 0 ) {
return ret ;
} else if ( ret = = 1 ) {
RTMPPacket rpkt = { 0 } ;
if ( ( ret = ff_rtmp_packet_read_internal ( rt - > stream , & rpkt ,
rt - > chunk_size ,
rt - > prev_pkt [ 0 ] , c ) ) < = 0 )
return ret ;
if ( ( ret = rtmp_parse_result ( s , rt , & rpkt ) ) < 0 )
return ret ;
ff_rtmp_packet_destroy ( & rpkt ) ;
}
2009-12-04 17:52:16 +01:00
return size ;
2009-07-31 08:49:36 +02:00
}
2012-04-15 21:49:48 +02:00
# define OFFSET(x) offsetof(RTMPContext, x)
# define DEC AV_OPT_FLAG_DECODING_PARAM
# define ENC AV_OPT_FLAG_ENCODING_PARAM
static const AVOption rtmp_options [ ] = {
{ " rtmp_app " , " Name of application to connect to on the RTMP server " , OFFSET ( app ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-06-13 15:02:03 +02:00
{ " rtmp_buffer " , " Set buffer time in milliseconds. The default is 3000. " , OFFSET ( client_buffer_time ) , AV_OPT_TYPE_INT , { 3000 } , 0 , INT_MAX , DEC | ENC } ,
2012-06-08 13:16:34 +02:00
{ " rtmp_conn " , " Append arbitrary AMF data to the Connect message " , OFFSET ( conn ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-05-09 02:12:15 +02:00
{ " rtmp_flashver " , " Version of the Flash plugin used to run the SWF player. " , OFFSET ( flashver ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-06-18 14:55:55 +02:00
{ " rtmp_flush_interval " , " Number of packets flushed in the same request (RTMPT only). " , OFFSET ( flush_interval ) , AV_OPT_TYPE_INT , { 10 } , 0 , INT_MAX , ENC } ,
2012-05-05 19:33:26 +02:00
{ " rtmp_live " , " Specify that the media is a live stream. " , OFFSET ( live ) , AV_OPT_TYPE_INT , { - 2 } , INT_MIN , INT_MAX , DEC , " rtmp_live " } ,
{ " any " , " both " , 0 , AV_OPT_TYPE_CONST , { - 2 } , 0 , 0 , DEC , " rtmp_live " } ,
{ " live " , " live stream " , 0 , AV_OPT_TYPE_CONST , { - 1 } , 0 , 0 , DEC , " rtmp_live " } ,
{ " recorded " , " recorded stream " , 0 , AV_OPT_TYPE_CONST , { 0 } , 0 , 0 , DEC , " rtmp_live " } ,
2012-07-24 16:29:40 +02:00
{ " rtmp_pageurl " , " URL of the web page in which the media was embedded. By default no value will be sent. " , OFFSET ( pageurl ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC } ,
2012-04-15 21:50:50 +02:00
{ " rtmp_playpath " , " Stream identifier to play or to publish " , OFFSET ( playpath ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-05-09 02:12:16 +02:00
{ " rtmp_swfurl " , " URL of the SWF player. By default no value will be sent " , OFFSET ( swfurl ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-07-24 16:29:38 +02:00
{ " rtmp_tcurl " , " URL of the target stream. Defaults to proto://host[:port]/app. " , OFFSET ( tcurl ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-04-15 21:49:48 +02:00
{ NULL } ,
} ;
static const AVClass rtmp_class = {
. class_name = " rtmp " ,
. item_name = av_default_item_name ,
. option = rtmp_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
2011-01-25 23:03:28 +01:00
URLProtocol ff_rtmp_protocol = {
2011-12-01 10:53:57 +01:00
. name = " rtmp " ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
2011-12-01 10:44:21 +01:00
. priv_data_size = sizeof ( RTMPContext ) ,
2011-12-30 10:38:05 +01:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2012-04-15 21:49:48 +02:00
. priv_data_class = & rtmp_class ,
2009-07-31 08:49:36 +02:00
} ;
2012-06-17 20:24:43 +02:00
2012-07-19 14:13:58 +02:00
static const AVClass rtmpe_class = {
. class_name = " rtmpe " ,
. item_name = av_default_item_name ,
. option = rtmp_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
URLProtocol ff_rtmpe_protocol = {
. name = " rtmpe " ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. priv_data_size = sizeof ( RTMPContext ) ,
. flags = URL_PROTOCOL_FLAG_NETWORK ,
. priv_data_class = & rtmpe_class ,
} ;
2012-07-17 12:02:42 +02:00
static const AVClass rtmps_class = {
. class_name = " rtmps " ,
. item_name = av_default_item_name ,
. option = rtmp_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
URLProtocol ff_rtmps_protocol = {
. name = " rtmps " ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. priv_data_size = sizeof ( RTMPContext ) ,
. flags = URL_PROTOCOL_FLAG_NETWORK ,
. priv_data_class = & rtmps_class ,
} ;
2012-06-17 20:24:43 +02:00
static const AVClass rtmpt_class = {
. class_name = " rtmpt " ,
. item_name = av_default_item_name ,
. option = rtmp_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
URLProtocol ff_rtmpt_protocol = {
. name = " rtmpt " ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. priv_data_size = sizeof ( RTMPContext ) ,
. flags = URL_PROTOCOL_FLAG_NETWORK ,
. priv_data_class = & rtmpt_class ,
} ;
2012-07-17 12:02:43 +02:00
2012-07-20 16:36:47 +02:00
static const AVClass rtmpte_class = {
. class_name = " rtmpte " ,
. item_name = av_default_item_name ,
. option = rtmp_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
URLProtocol ff_rtmpte_protocol = {
. name = " rtmpte " ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. priv_data_size = sizeof ( RTMPContext ) ,
. flags = URL_PROTOCOL_FLAG_NETWORK ,
. priv_data_class = & rtmpte_class ,
} ;
2012-07-17 12:02:43 +02:00
static const AVClass rtmpts_class = {
. class_name = " rtmpts " ,
. item_name = av_default_item_name ,
. option = rtmp_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
URLProtocol ff_rtmpts_protocol = {
. name = " rtmpts " ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. priv_data_size = sizeof ( RTMPContext ) ,
. flags = URL_PROTOCOL_FLAG_NETWORK ,
. priv_data_class = & rtmpts_class ,
} ;