2009-07-31 09:49:36 +03:00
/*
* RTMP network protocol
* Copyright ( c ) 2009 Kostya Shishkov
*
2011-03-18 19:35:10 +02:00
* This file is part of Libav .
2009-07-31 09:49:36 +03:00
*
2011-03-18 19:35:10 +02:00
* Libav is free software ; you can redistribute it and / or
2009-07-31 09:49:36 +03: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 19:35:10 +02:00
* Libav is distributed in the hope that it will be useful ,
2009-07-31 09:49:36 +03: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 19:35:10 +02:00
* License along with Libav ; if not , write to the Free Software
2009-07-31 09:49:36 +03:00
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
2010-04-20 17:45:34 +03:00
* @ file
2009-07-31 09:49:36 +03:00
* RTMP protocol
*/
# include "libavcodec/bytestream.h"
# include "libavutil/avstring.h"
2011-11-27 17:04:16 +03:00
# include "libavutil/intfloat.h"
2009-07-31 09:49:36 +03:00
# include "libavutil/lfg.h"
2012-04-15 22:49:48 +03:00
# include "libavutil/opt.h"
2009-07-31 09:49:36 +03:00
# include "libavutil/sha.h"
# include "avformat.h"
2010-03-15 01:59:48 +02:00
# include "internal.h"
2009-07-31 09:49:36 +03:00
# include "network.h"
# include "flv.h"
# include "rtmp.h"
2012-07-19 15:13:58 +03:00
# include "rtmpcrypt.h"
2009-07-31 09:49:36 +03:00
# include "rtmppkt.h"
2011-03-31 17:25:10 +03:00
# include "url.h"
2009-07-31 09:49:36 +03:00
2009-12-11 19:13:35 +02:00
//#define DEBUG
2012-04-15 22:49:48 +03:00
# define APP_MAX_LENGTH 128
2012-04-15 22:50:50 +03:00
# define PLAYPATH_MAX_LENGTH 256
2012-05-09 03:12:14 +03:00
# define TCURL_MAX_LENGTH 512
2012-05-09 03:12:15 +03:00
# define FLASHVER_MAX_LENGTH 64
2012-04-15 22:49:48 +03:00
2009-07-31 09:49:36 +03:00
/** RTMP protocol handler state */
typedef enum {
STATE_START , ///< client has not done anything yet
STATE_HANDSHAKED , ///< client has performed handshake
2009-12-04 18:52:16 +02:00
STATE_FCPUBLISH , ///< client FCPublishing stream (for output)
2009-07-31 09:49:36 +03:00
STATE_PLAYING , ///< client has started receiving multimedia data from server
2009-12-04 18:52:16 +02:00
STATE_PUBLISHING , ///< client has started sending multimedia data to server (for output)
2009-12-11 13:37:21 +02:00
STATE_STOPPED , ///< the broadcast has been stopped
2009-07-31 09:49:36 +03:00
} ClientState ;
2012-08-08 15:36:39 +03:00
typedef struct TrackedMethod {
char * name ;
int id ;
} TrackedMethod ;
2009-07-31 09:49:36 +03:00
/** protocol handler context */
typedef struct RTMPContext {
2012-04-15 22:49:48 +03:00
const AVClass * class ;
2009-07-31 09:49:36 +03: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 14:55:10 +02:00
int is_input ; ///< input/output flag
2012-04-15 22:50:50 +03:00
char * playpath ; ///< stream identifier to play (with possible "mp4:" prefix)
2012-05-05 20:33:26 +03:00
int live ; ///< 0: recorded, -1: live, -2: both
2012-04-15 22:49:48 +03:00
char * app ; ///< name of application
2012-06-08 14:16:34 +03:00
char * conn ; ///< append arbitrary AMF data to the Connect message
2009-07-31 09:49:36 +03: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 15:55:55 +03:00
int flv_nb_packets ; ///< number of flv packets published
2009-12-04 18:52:16 +02:00
RTMPPacket out_pkt ; ///< rtmp packet, created from flv a/v or metadata (for output)
2010-02-18 18:27:18 +02: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 16:00:52 +03:00
int skip_bytes ; ///< number of bytes to skip from the input FLV stream in the next write call
2011-09-21 23:21:30 +03:00
uint8_t flv_header [ 11 ] ; ///< partial incoming flv packet header
int flv_header_bytes ; ///< number of initialized bytes in flv_header
2011-11-13 00:28:58 +03:00
int nb_invokes ; ///< keeps track of invoke messages
2012-05-09 03:12:14 +03:00
char * tcurl ; ///< url of the target stream
2012-05-09 03:12:15 +03:00
char * flashver ; ///< version of the flash plugin
2012-05-09 03:12:16 +03:00
char * swfurl ; ///< url of the swf player
2012-07-24 17:29:40 +03:00
char * pageurl ; ///< url of the web page
2012-08-07 23:02:27 +03:00
char * subscribe ; ///< name of live stream to subscribe
2012-06-13 15:47:26 +03:00
int server_bw ; ///< server bandwidth
2012-06-13 15:48:02 +03:00
int client_buffer_time ; ///< client buffer time in ms
2012-06-18 15:55:55 +03:00
int flush_interval ; ///< number of packets flushed in the same request (RTMPT only)
2012-07-19 15:13:58 +03:00
int encrypted ; ///< use an encrypted connection (RTMPE only)
2012-08-08 15:36:39 +03:00
TrackedMethod * tracked_methods ; ///< tracked methods buffer
int nb_tracked_methods ; ///< number of tracked methods
int tracked_methods_size ; ///< size of the tracked methods buffer
2009-07-31 09:49:36 +03: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-08-08 15:36:39 +03:00
static int add_tracked_method ( RTMPContext * rt , const char * name , int id )
{
void * ptr ;
if ( rt - > nb_tracked_methods + 1 > rt - > tracked_methods_size ) {
rt - > tracked_methods_size = ( rt - > nb_tracked_methods + 1 ) * 2 ;
ptr = av_realloc ( rt - > tracked_methods ,
rt - > tracked_methods_size * sizeof ( * rt - > tracked_methods ) ) ;
if ( ! ptr )
return AVERROR ( ENOMEM ) ;
rt - > tracked_methods = ptr ;
}
rt - > tracked_methods [ rt - > nb_tracked_methods ] . name = av_strdup ( name ) ;
if ( ! rt - > tracked_methods [ rt - > nb_tracked_methods ] . name )
return AVERROR ( ENOMEM ) ;
rt - > tracked_methods [ rt - > nb_tracked_methods ] . id = id ;
rt - > nb_tracked_methods + + ;
return 0 ;
}
static void del_tracked_method ( RTMPContext * rt , int index )
{
memmove ( & rt - > tracked_methods [ index ] , & rt - > tracked_methods [ index + 1 ] ,
sizeof ( * rt - > tracked_methods ) * ( rt - > nb_tracked_methods - index - 1 ) ) ;
rt - > nb_tracked_methods - - ;
}
static void free_tracked_methods ( RTMPContext * rt )
{
int i ;
for ( i = 0 ; i < rt - > nb_tracked_methods ; i + + )
av_free ( rt - > tracked_methods [ i ] . name ) ;
av_free ( rt - > tracked_methods ) ;
}
static int rtmp_send_packet ( RTMPContext * rt , RTMPPacket * pkt , int track )
{
int ret ;
if ( pkt - > type = = RTMP_PT_INVOKE & & track ) {
GetByteContext gbc ;
char name [ 128 ] ;
double pkt_id ;
int len ;
bytestream2_init ( & gbc , pkt - > data , pkt - > data_size ) ;
if ( ( ret = ff_amf_read_string ( & gbc , name , sizeof ( name ) , & len ) ) < 0 )
goto fail ;
if ( ( ret = ff_amf_read_number ( & gbc , & pkt_id ) ) < 0 )
goto fail ;
if ( ( ret = add_tracked_method ( rt , name , pkt_id ) ) < 0 )
goto fail ;
}
ret = ff_rtmp_packet_write ( rt - > stream , pkt , rt - > chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
fail :
ff_rtmp_packet_destroy ( pkt ) ;
return ret ;
}
2012-06-08 14:16:34 +03:00
static int rtmp_write_amf_data ( URLContext * s , char * param , uint8_t * * p )
{
2012-06-13 10:51:22 +03:00
char * field , * value ;
2012-06-08 14:16:34 +03: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 10:51:22 +03:00
field = param + 3 ;
value = strchr ( field , ' : ' ) ;
if ( ! value )
goto fail ;
* value = ' \0 ' ;
value + + ;
2012-06-08 14:16:34 +03: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 09:49:36 +03:00
/**
2010-06-30 18:38:06 +03:00
* Generate ' connect ' call and send it to the server .
2009-07-31 09:49:36 +03:00
*/
2012-05-23 23:45:03 +03:00
static int gen_connect ( URLContext * s , RTMPContext * rt )
2009-07-31 09:49:36 +03:00
{
RTMPPacket pkt ;
2012-05-09 03:12:15 +03:00
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 4096 ) ) < 0 )
return ret ;
2009-07-31 09:49:36 +03:00
p = pkt . data ;
ff_amf_write_string ( & p , " connect " ) ;
2011-12-05 13:35:06 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-07-31 09:49:36 +03:00
ff_amf_write_object_start ( & p ) ;
ff_amf_write_field_name ( & p , " app " ) ;
2009-12-03 17:47:00 +02:00
ff_amf_write_string ( & p , rt - > app ) ;
2009-07-31 09:49:36 +03:00
2012-05-09 03:12:15 +03:00
if ( ! rt - > is_input ) {
2009-12-04 18:52:16 +02:00
ff_amf_write_field_name ( & p , " type " ) ;
ff_amf_write_string ( & p , " nonprivate " ) ;
}
2009-07-31 09:49:36 +03:00
ff_amf_write_field_name ( & p , " flashVer " ) ;
2012-05-09 03:12:15 +03:00
ff_amf_write_string ( & p , rt - > flashver ) ;
2012-05-09 03:12:16 +03:00
if ( rt - > swfurl ) {
ff_amf_write_field_name ( & p , " swfUrl " ) ;
ff_amf_write_string ( & p , rt - > swfurl ) ;
}
2009-07-31 09:49:36 +03:00
ff_amf_write_field_name ( & p , " tcUrl " ) ;
2012-05-09 03:12:14 +03:00
ff_amf_write_string ( & p , rt - > tcurl ) ;
2009-12-04 18:52:16 +02:00
if ( rt - > is_input ) {
2009-12-04 18:52:42 +02: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 23:50:38 +03: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 18:52:42 +02:00
ff_amf_write_field_name ( & p , " audioCodecs " ) ;
2012-04-02 23:50:38 +03:00
ff_amf_write_number ( & p , 4071.0 ) ;
2009-12-04 18:52:42 +02: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 17:29:40 +03:00
if ( rt - > pageurl ) {
ff_amf_write_field_name ( & p , " pageUrl " ) ;
ff_amf_write_string ( & p , rt - > pageurl ) ;
}
2009-12-04 18:52:16 +02:00
}
2009-07-31 09:49:36 +03:00
ff_amf_write_object_end ( & p ) ;
2012-06-08 14:16:34 +03:00
if ( rt - > conn ) {
2012-06-13 10:51:22 +03:00
char * param = rt - > conn ;
2012-06-08 14:16:34 +03:00
// Write arbitrary AMF data to the Connect message.
while ( param ! = NULL ) {
2012-06-13 10:51:22 +03:00
char * sep ;
param + = strspn ( param , " " ) ;
if ( ! * param )
break ;
sep = strchr ( param , ' ' ) ;
if ( sep )
* sep = ' \0 ' ;
2012-06-08 14:16:34 +03:00
if ( ( ret = rtmp_write_amf_data ( s , param , & p ) ) < 0 ) {
// Invalid AMF parameter.
ff_rtmp_packet_destroy ( & pkt ) ;
return ret ;
}
2012-06-13 10:51:22 +03:00
if ( sep )
param = sep + 1 ;
else
break ;
2012-06-08 14:16:34 +03:00
}
}
2009-07-31 09:49:36 +03:00
pkt . data_size = p - pkt . data ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 1 ) ;
2009-07-31 09:49:36 +03:00
}
2009-12-04 18:52:16 +02:00
/**
2010-06-30 18:38:06 +03:00
* Generate ' releaseStream ' call and send it to the server . It should make
2009-12-04 18:52:16 +02:00
* the server release some channel for media streams .
*/
2012-05-23 23:45:03 +03:00
static int gen_release_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 18:52:16 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-12-04 18:52:16 +02:00
2012-05-23 23:45:03 +03: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 18:52:16 +02:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Releasing stream... \n " ) ;
2009-12-04 18:52:16 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " releaseStream " ) ;
2011-11-13 00:28:58 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 18:52:16 +02:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2009-12-04 18:52:16 +02:00
}
/**
2010-06-30 18:38:06 +03:00
* Generate ' FCPublish ' call and send it to the server . It should make
2009-12-04 18:52:16 +02:00
* the server preapare for receiving media streams .
*/
2012-05-23 23:45:03 +03:00
static int gen_fcpublish_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 18:52:16 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-12-04 18:52:16 +02:00
2012-05-23 23:45:03 +03: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 18:52:16 +02:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " FCPublish stream... \n " ) ;
2009-12-04 18:52:16 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " FCPublish " ) ;
2011-11-13 00:28:58 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 18:52:16 +02:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2009-12-04 18:52:16 +02:00
}
/**
2010-06-30 18:38:06 +03:00
* Generate ' FCUnpublish ' call and send it to the server . It should make
2009-12-04 18:52:16 +02:00
* the server destroy stream .
*/
2012-05-23 23:45:03 +03:00
static int gen_fcunpublish_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 18:52:16 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-12-04 18:52:16 +02:00
2012-05-23 23:45:03 +03: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 18:52:16 +02:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " UnPublishing stream... \n " ) ;
2009-12-04 18:52:16 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " FCUnpublish " ) ;
2011-11-13 00:28:58 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 18:52:16 +02:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2009-12-04 18:52:16 +02:00
}
2009-07-31 09:49:36 +03:00
/**
2010-06-30 18:38:06 +03:00
* Generate ' createStream ' call and send it to the server . It should make
2009-07-31 09:49:36 +03:00
* the server allocate some channel for media streams .
*/
2012-05-23 23:45:03 +03:00
static int gen_create_stream ( URLContext * s , RTMPContext * rt )
2009-07-31 09:49:36 +03:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-07-31 09:49:36 +03:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Creating stream... \n " ) ;
2012-05-23 23:45:03 +03:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 25 ) ) < 0 )
return ret ;
2009-07-31 09:49:36 +03:00
p = pkt . data ;
ff_amf_write_string ( & p , " createStream " ) ;
2011-11-13 00:28:58 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 18:52:16 +02:00
ff_amf_write_null ( & p ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 1 ) ;
2009-12-04 18:52:16 +02:00
}
/**
2010-06-30 18:38:06 +03:00
* Generate ' deleteStream ' call and send it to the server . It should make
2009-12-04 18:52:16 +02:00
* the server remove some channel for media streams .
*/
2012-05-23 23:45:03 +03:00
static int gen_delete_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 18:52:16 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-12-04 18:52:16 +02:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Deleting stream... \n " ) ;
2012-05-23 23:45:03 +03:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 34 ) ) < 0 )
return ret ;
2009-12-04 18:52:16 +02:00
p = pkt . data ;
ff_amf_write_string ( & p , " deleteStream " ) ;
2011-12-05 13:35:06 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-07-31 09:49:36 +03:00
ff_amf_write_null ( & p ) ;
2009-12-04 18:52:16 +02:00
ff_amf_write_number ( & p , rt - > main_channel_id ) ;
2009-07-31 09:49:36 +03:00
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2009-07-31 09:49:36 +03:00
}
2012-06-13 15:48:02 +03: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 ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2012-06-13 15:48:02 +03:00
}
2009-07-31 09:49:36 +03:00
/**
2010-06-30 18:38:06 +03:00
* Generate ' play ' call and send it to the server , then ping the server
2009-07-31 09:49:36 +03:00
* to start actual playing .
*/
2012-05-23 23:45:03 +03:00
static int gen_play ( URLContext * s , RTMPContext * rt )
2009-07-31 09:49:36 +03:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-07-31 09:49:36 +03:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Sending play command for '%s' \n " , rt - > playpath ) ;
2012-05-23 23:45:03 +03: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 09:49:36 +03:00
pkt . extra = rt - > main_channel_id ;
p = pkt . data ;
ff_amf_write_string ( & p , " play " ) ;
2011-12-05 13:35:06 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-07-31 09:49:36 +03:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
2012-05-05 20:33:26 +03:00
ff_amf_write_number ( & p , rt - > live ) ;
2009-07-31 09:49:36 +03:00
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 1 ) ;
2009-07-31 09:49:36 +03:00
}
2009-12-04 18:52:16 +02:00
/**
2010-06-30 18:38:06 +03:00
* Generate ' publish ' call and send it to the server .
2009-12-04 18:52:16 +02:00
*/
2012-05-23 23:45:03 +03:00
static int gen_publish ( URLContext * s , RTMPContext * rt )
2009-12-04 18:52:16 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-12-04 18:52:16 +02:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Sending publish command for '%s' \n " , rt - > playpath ) ;
2012-05-23 23:45:03 +03: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 18:52:16 +02:00
pkt . extra = rt - > main_channel_id ;
p = pkt . data ;
ff_amf_write_string ( & p , " publish " ) ;
2011-12-05 13:35:06 +03:00
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
2009-12-04 18:52:16 +02:00
ff_amf_write_null ( & p ) ;
ff_amf_write_string ( & p , rt - > playpath ) ;
ff_amf_write_string ( & p , " live " ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 1 ) ;
2009-12-04 18:52:16 +02:00
}
2009-07-31 09:49:36 +03:00
/**
2010-06-30 18:38:06 +03:00
* Generate ping reply and send it to the server .
2009-07-31 09:49:36 +03:00
*/
2012-05-23 23:45:03 +03:00
static int gen_pong ( URLContext * s , RTMPContext * rt , RTMPPacket * ppkt )
2009-07-31 09:49:36 +03:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2012-07-26 21:45:42 +03:00
if ( ppkt - > data_size < 6 ) {
av_log ( s , AV_LOG_ERROR , " Too short ping packet (%d) \n " ,
ppkt - > data_size ) ;
return AVERROR_INVALIDDATA ;
}
2012-05-23 23:45:03 +03:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_PING ,
ppkt - > timestamp + 1 , 6 ) ) < 0 )
return ret ;
2009-07-31 09:49:36 +03:00
p = pkt . data ;
bytestream_put_be16 ( & p , 7 ) ;
2009-12-16 14:49:38 +02:00
bytestream_put_be32 ( & p , AV_RB32 ( ppkt - > data + 2 ) ) ;
2012-05-23 23:45:03 +03:00
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2009-07-31 09:49:36 +03:00
}
2012-03-07 21:21:16 +03:00
/**
* Generate server bandwidth message and send it to the server .
*/
2012-05-23 23:45:03 +03:00
static int gen_server_bw ( URLContext * s , RTMPContext * rt )
2012-03-07 21:21:16 +03:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03: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 21:21:16 +03:00
p = pkt . data ;
2012-06-13 15:47:26 +03:00
bytestream_put_be32 ( & p , rt - > server_bw ) ;
2012-05-23 23:45:03 +03:00
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2012-03-07 21:21:16 +03:00
}
2012-05-09 01:58:09 +03:00
/**
* Generate check bandwidth message and send it to the server .
*/
2012-05-23 23:45:03 +03:00
static int gen_check_bw ( URLContext * s , RTMPContext * rt )
2012-05-09 01:58:09 +03:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03:00
int ret ;
2012-05-09 01:58:09 +03:00
2012-05-23 23:45:03 +03:00
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
0 , 21 ) ) < 0 )
return ret ;
2012-05-09 01:58:09 +03:00
p = pkt . data ;
ff_amf_write_string ( & p , " _checkbw " ) ;
2012-08-08 12:49:02 +03:00
ff_amf_write_number ( & p , RTMP_NOTIFICATION ) ;
2012-05-09 01:58:09 +03:00
ff_amf_write_null ( & p ) ;
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2012-05-09 01:58:09 +03:00
}
2010-02-18 18:27:18 +02:00
/**
2010-06-30 18:38:06 +03:00
* Generate report on bytes read so far and send it to the server .
2010-02-18 18:27:18 +02:00
*/
2012-05-23 23:45:03 +03:00
static int gen_bytes_read ( URLContext * s , RTMPContext * rt , uint32_t ts )
2010-02-18 18:27:18 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 23:45:03 +03: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 18:27:18 +02:00
p = pkt . data ;
bytestream_put_be32 ( & p , rt - > bytes_read ) ;
2012-05-23 23:45:03 +03:00
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 0 ) ;
2010-02-18 18:27:18 +02:00
}
2012-08-07 23:02:27 +03:00
static int gen_fcsubscribe_stream ( URLContext * s , RTMPContext * rt ,
const char * subscribe )
2012-08-07 22:51:46 +03:00
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
2012-08-07 23:02:27 +03:00
0 , 27 + strlen ( subscribe ) ) ) < 0 )
2012-08-07 22:51:46 +03:00
return ret ;
p = pkt . data ;
ff_amf_write_string ( & p , " FCSubscribe " ) ;
ff_amf_write_number ( & p , + + rt - > nb_invokes ) ;
ff_amf_write_null ( & p ) ;
2012-08-07 23:02:27 +03:00
ff_amf_write_string ( & p , subscribe ) ;
2012-08-07 22:51:46 +03:00
2012-08-08 15:36:39 +03:00
return rtmp_send_packet ( rt , & pkt , 1 ) ;
2012-08-07 22:51:46 +03:00
}
2012-07-05 14:05:46 +03: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 09:49:36 +03:00
{
struct AVSHA * sha ;
uint8_t hmac_buf [ 64 + 32 ] = { 0 } ;
int i ;
sha = av_mallocz ( av_sha_size ) ;
2012-05-23 19:55:34 +03:00
if ( ! sha )
return AVERROR ( ENOMEM ) ;
2009-07-31 09:49:36 +03: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 19:55:34 +03:00
return 0 ;
2009-07-31 09:49:36 +03:00
}
2012-07-05 14:06:07 +03: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 09:49:36 +03:00
/**
2010-06-30 18:38:06 +03:00
* Put HMAC - SHA2 digest of packet data ( except for the bytes where this digest
2009-07-31 09:49:36 +03:00
* will be stored ) into that packet .
*
* @ param buf handshake data ( 1536 bytes )
2012-07-19 15:13:58 +03:00
* @ param encrypted use an encrypted connection ( RTMPE )
2009-07-31 09:49:36 +03:00
* @ return offset to the digest inside input data
*/
2012-07-19 15:13:58 +03:00
static int rtmp_handshake_imprint_with_digest ( uint8_t * buf , int encrypted )
2009-07-31 09:49:36 +03:00
{
2012-07-05 14:06:07 +03:00
int ret , digest_pos ;
2009-07-31 09:49:36 +03:00
2012-07-19 15:13:58 +03: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 09:49:36 +03:00
2012-07-05 14:05:46 +03: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 19:55:34 +03:00
if ( ret < 0 )
return ret ;
2009-07-31 09:49:36 +03:00
return digest_pos ;
}
/**
2010-06-30 18:38:06 +03:00
* Verify that the received server response has the expected digest value .
2009-07-31 09:49:36 +03: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 14:06:07 +03:00
int ret , digest_pos ;
2009-07-31 09:49:36 +03:00
2012-07-05 14:06:07 +03:00
digest_pos = ff_rtmp_calc_digest_pos ( buf , off , 728 , off + 4 ) ;
2009-07-31 09:49:36 +03:00
2012-07-05 14:05:46 +03: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 19:55:34 +03:00
if ( ret < 0 )
return ret ;
2009-07-31 09:49:36 +03:00
if ( ! memcmp ( digest , buf + digest_pos , 32 ) )
return digest_pos ;
return 0 ;
}
/**
2010-06-30 18:38:06 +03:00
* Perform handshake with the server by means of exchanging pseudorandom data
2009-07-31 09:49:36 +03: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 15:13:58 +03:00
uint8_t digest [ 32 ] , signature [ 32 ] ;
int ret , type = 0 ;
2009-07-31 09:49:36 +03:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Handshaking... \n " ) ;
2009-07-31 09:49:36 +03: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 15:13:58 +03:00
2012-07-24 14:46:28 +03:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 15:13:58 +03: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 14:46:28 +03:00
client_pos = rtmp_handshake_imprint_with_digest ( tosend + 1 , rt - > encrypted ) ;
2012-05-23 19:55:34 +03:00
if ( client_pos < 0 )
return client_pos ;
2009-07-31 09:49:36 +03:00
2012-05-24 14:48:25 +03: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 14:48:42 +03:00
if ( ( ret = ffurl_read_complete ( rt - > stream , serverdata ,
RTMP_HANDSHAKE_PACKET_SIZE + 1 ) ) < 0 ) {
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_ERROR , " Cannot read RTMP handshake response \n " ) ;
2012-05-24 14:48:42 +03:00
return ret ;
2009-07-31 09:49:36 +03:00
}
2012-05-24 14:48:42 +03:00
if ( ( ret = ffurl_read_complete ( rt - > stream , clientdata ,
RTMP_HANDSHAKE_PACKET_SIZE ) ) < 0 ) {
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_ERROR , " Cannot read RTMP handshake response \n " ) ;
2012-05-24 14:48:42 +03:00
return ret ;
2009-07-31 09:49:36 +03:00
}
2012-07-19 15:13:58 +03:00
av_log ( s , AV_LOG_DEBUG , " Type answer %d \n " , serverdata [ 0 ] ) ;
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Server version %d.%d.%d.%d \n " ,
2009-07-31 09:49:36 +03:00
serverdata [ 5 ] , serverdata [ 6 ] , serverdata [ 7 ] , serverdata [ 8 ] ) ;
2010-01-12 08:44:49 +02:00
if ( rt - > is_input & & serverdata [ 5 ] > = 3 ) {
2009-12-04 18:52:42 +02:00
server_pos = rtmp_validate_digest ( serverdata + 1 , 772 ) ;
2012-05-23 19:55:34 +03:00
if ( server_pos < 0 )
return server_pos ;
2009-07-31 09:49:36 +03:00
if ( ! server_pos ) {
2012-07-19 15:13:58 +03:00
type = 1 ;
2009-12-04 18:52:42 +02:00
server_pos = rtmp_validate_digest ( serverdata + 1 , 8 ) ;
2012-05-23 19:55:34 +03:00
if ( server_pos < 0 )
return server_pos ;
2009-12-04 18:52:42 +02:00
if ( ! server_pos ) {
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_ERROR , " Server response validating failed \n " ) ;
2012-05-23 19:55:52 +03:00
return AVERROR ( EIO ) ;
2009-12-04 18:52:42 +02:00
}
2009-07-31 09:49:36 +03:00
}
2012-07-05 14:05:46 +03:00
ret = ff_rtmp_calc_digest ( tosend + 1 + client_pos , 32 , 0 ,
rtmp_server_key , sizeof ( rtmp_server_key ) ,
digest ) ;
2012-05-23 19:55:34 +03:00
if ( ret < 0 )
return ret ;
2012-07-05 14:05:46 +03:00
ret = ff_rtmp_calc_digest ( clientdata , RTMP_HANDSHAKE_PACKET_SIZE - 32 ,
2012-07-19 15:13:58 +03:00
0 , digest , 32 , signature ) ;
2012-05-23 19:55:34 +03:00
if ( ret < 0 )
return ret ;
2012-07-24 14:46:28 +03:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 15:13:58 +03: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 13:53:28 +02:00
av_log ( s , AV_LOG_ERROR , " Signature mismatch \n " ) ;
2012-05-23 19:55:52 +03:00
return AVERROR ( EIO ) ;
2009-12-04 18:52:42 +02:00
}
2009-07-31 09:49:36 +03:00
2009-12-04 18:52:42 +02:00
for ( i = 0 ; i < RTMP_HANDSHAKE_PACKET_SIZE ; i + + )
tosend [ i ] = av_lfg_get ( & rnd ) > > 24 ;
2012-07-05 14:05:46 +03:00
ret = ff_rtmp_calc_digest ( serverdata + 1 + server_pos , 32 , 0 ,
rtmp_player_key , sizeof ( rtmp_player_key ) ,
digest ) ;
2012-05-23 19:55:34 +03:00
if ( ret < 0 )
return ret ;
2012-07-05 14:05:46 +03:00
ret = ff_rtmp_calc_digest ( tosend , RTMP_HANDSHAKE_PACKET_SIZE - 32 , 0 ,
digest , 32 ,
tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32 ) ;
2012-05-23 19:55:34 +03:00
if ( ret < 0 )
return ret ;
2009-12-04 18:52:42 +02:00
2012-07-24 14:46:28 +03:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 15:13:58 +03: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 18:52:42 +02:00
// write reply back to the server
2012-05-24 14:48:25 +03:00
if ( ( ret = ffurl_write ( rt - > stream , tosend ,
RTMP_HANDSHAKE_PACKET_SIZE ) ) < 0 )
return ret ;
2012-07-19 15:13:58 +03:00
2012-07-24 14:46:28 +03:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 15:13:58 +03:00
/* Set RC4 keys for encryption and update the keystreams. */
if ( ( ret = ff_rtmpe_update_keystream ( rt - > stream ) ) < 0 )
return ret ;
}
2009-12-04 18:52:16 +02:00
} else {
2012-07-24 14:46:28 +03:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 15:13:58 +03: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 14:48:25 +03:00
if ( ( ret = ffurl_write ( rt - > stream , serverdata + 1 ,
RTMP_HANDSHAKE_PACKET_SIZE ) ) < 0 )
return ret ;
2012-07-19 15:13:58 +03:00
2012-07-24 14:46:28 +03:00
if ( rt - > encrypted & & CONFIG_FFRTMPCRYPT_PROTOCOL ) {
2012-07-19 15:13:58 +03:00
/* Set RC4 keys for encryption and update the keystreams. */
if ( ( ret = ff_rtmpe_update_keystream ( rt - > stream ) ) < 0 )
return ret ;
}
2009-12-04 18:52:16 +02:00
}
2009-07-31 09:49:36 +03:00
return 0 ;
}
2012-07-21 13:59:52 +03:00
static int handle_chunk_size ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
int ret ;
2012-07-26 15:05:58 +03:00
if ( pkt - > data_size < 4 ) {
2012-07-21 13:59:52 +03:00
av_log ( s , AV_LOG_ERROR ,
2012-07-26 15:05:58 +03:00
" Too short chunk size change packet (%d) \n " ,
2012-07-21 13:59:52 +03:00
pkt - > data_size ) ;
2012-07-25 21:51:08 +03:00
return AVERROR_INVALIDDATA ;
2012-07-21 13:59:52 +03: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 21:51:08 +03:00
return AVERROR_INVALIDDATA ;
2012-07-21 13:59:52 +03:00
}
av_log ( s , AV_LOG_DEBUG , " New chunk size = %d \n " , rt - > chunk_size ) ;
return 0 ;
}
2012-07-21 13:59:51 +03:00
static int handle_ping ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
int t , ret ;
2012-07-26 21:45:42 +03:00
if ( pkt - > data_size < 2 ) {
av_log ( s , AV_LOG_ERROR , " Too short ping packet (%d) \n " ,
pkt - > data_size ) ;
return AVERROR_INVALIDDATA ;
}
2012-07-21 13:59:51 +03:00
t = AV_RB16 ( pkt - > data ) ;
if ( t = = 6 ) {
if ( ( ret = gen_pong ( s , rt , pkt ) ) < 0 )
return ret ;
}
return 0 ;
}
2012-07-21 13:59:50 +03: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 21:51:09 +03:00
return AVERROR_INVALIDDATA ;
2012-07-21 13:59:50 +03:00
}
2012-07-25 21:51:11 +03: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 13:59:50 +03:00
return 0 ;
}
2012-07-21 13:59:49 +03:00
static int handle_server_bw ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
2012-07-26 15:05:18 +03: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 13:59:49 +03: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 21:51:10 +03:00
return AVERROR_INVALIDDATA ;
2012-07-21 13:59:49 +03:00
}
av_log ( s , AV_LOG_DEBUG , " Server bandwidth = %d \n " , rt - > server_bw ) ;
return 0 ;
}
2012-07-21 13:59:58 +03:00
static int handle_invoke ( URLContext * s , RTMPPacket * pkt )
2009-07-31 09:49:36 +03:00
{
2012-07-21 13:59:58 +03:00
RTMPContext * rt = s - > priv_data ;
2009-07-31 09:49:36 +03:00
int i , t ;
const uint8_t * data_end = pkt - > data + pkt - > data_size ;
2012-08-08 15:36:39 +03:00
char * tracked_method = NULL ;
int ret = 0 ;
2009-07-31 09:49:36 +03:00
2012-07-21 13:59:58 +03: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 19:13:35 +02:00
2012-07-21 13:59:58 +03: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 ) ) {
2012-08-08 15:36:39 +03:00
GetByteContext gbc ;
double pkt_id ;
bytestream2_init ( & gbc , pkt - > data + 10 , pkt - > data_size ) ;
if ( ( ret = ff_amf_read_number ( & gbc , & pkt_id ) ) < 0 )
return ret ;
for ( i = 0 ; i < rt - > nb_tracked_methods ; i + + ) {
if ( rt - > tracked_methods [ i ] . id ! = pkt_id )
continue ;
tracked_method = rt - > tracked_methods [ i ] . name ;
del_tracked_method ( rt , i ) ;
break ;
}
if ( ! tracked_method ) {
/* Ignore this reply when the current method is not tracked. */
return 0 ;
}
if ( ! memcmp ( tracked_method , " connect " , 7 ) ) {
if ( ! rt - > is_input ) {
if ( ( ret = gen_release_stream ( s , rt ) ) < 0 )
goto invoke_fail ;
if ( ( ret = gen_fcpublish_stream ( s , rt ) ) < 0 )
goto invoke_fail ;
} else {
if ( ( ret = gen_server_bw ( s , rt ) ) < 0 )
goto invoke_fail ;
}
if ( ( ret = gen_create_stream ( s , rt ) ) < 0 )
goto invoke_fail ;
if ( rt - > is_input ) {
/* Send the FCSubscribe command when the name of live
* stream is defined by the user or if it ' s a live stream . */
if ( rt - > subscribe ) {
if ( ( ret = gen_fcsubscribe_stream ( s , rt ,
rt - > subscribe ) ) < 0 )
goto invoke_fail ;
} else if ( rt - > live = = - 1 ) {
if ( ( ret = gen_fcsubscribe_stream ( s , rt ,
rt - > playpath ) ) < 0 )
goto invoke_fail ;
2009-12-04 18:52:16 +02:00
}
2012-08-08 15:36:39 +03:00
}
} else if ( ! memcmp ( tracked_method , " createStream " , 12 ) ) {
//extract a number from the result
if ( pkt - > data [ 10 ] | | pkt - > data [ 19 ] ! = 5 | | pkt - > data [ 20 ] ) {
av_log ( s , AV_LOG_WARNING , " Unexpected reply on connect() \n " ) ;
} else {
rt - > main_channel_id = av_int2double ( AV_RB64 ( pkt - > data + 21 ) ) ;
}
if ( ! rt - > is_input ) {
if ( ( ret = gen_publish ( s , rt ) ) < 0 )
goto invoke_fail ;
} else {
if ( ( ret = gen_play ( s , rt ) ) < 0 )
goto invoke_fail ;
if ( ( ret = gen_buffer_time ( s , rt ) ) < 0 )
goto invoke_fail ;
}
2009-07-31 09:49:36 +03:00
}
2012-07-21 13:59:58 +03: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 ;
}
2012-08-08 15:36:39 +03:00
invoke_fail :
av_free ( tracked_method ) ;
return ret ;
2012-07-21 13:59:58 +03:00
}
/**
* 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 09:49:36 +03:00
break ;
2012-07-03 21:20:02 +03:00
case RTMP_PT_VIDEO :
case RTMP_PT_AUDIO :
2012-07-26 22:10:09 +03:00
case RTMP_PT_METADATA :
/* Audio, Video and Metadata packets are parsed in get_packet() */
2012-07-03 21:20:02 +03:00
break ;
2012-06-13 15:45:57 +03:00
default :
av_log ( s , AV_LOG_VERBOSE , " Unknown packet type received 0x%02X \n " , pkt - > type ) ;
break ;
2009-07-31 09:49:36 +03:00
}
return 0 ;
}
/**
2010-06-30 18:38:06 +03:00
* Interact with the server by receiving and sending RTMP packets until
2009-07-31 09:49:36 +03:00
* there is some significant data ( media data or expected status notification ) .
*
* @ param s reading context
2009-11-13 01:05:56 +02: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 09:49:36 +03: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 09:10:47 +02:00
uint8_t * p ;
const uint8_t * next ;
uint32_t data_size ;
uint32_t ts , cts , pts = 0 ;
2009-07-31 09:49:36 +03:00
2009-12-11 13:37:21 +02:00
if ( rt - > state = = STATE_STOPPED )
return AVERROR_EOF ;
2009-12-06 09:03:46 +02:00
for ( ; ; ) {
2011-05-25 19:08:29 +03:00
RTMPPacket rpkt = { 0 } ;
2009-07-31 09:49:36 +03:00
if ( ( ret = ff_rtmp_packet_read ( rt - > stream , & rpkt ,
2010-01-30 11:47:57 +02:00
rt - > chunk_size , rt - > prev_pkt [ 0 ] ) ) < = 0 ) {
2010-01-30 11:45:52 +02:00
if ( ret = = 0 ) {
2009-07-31 09:49:36 +03:00
return AVERROR ( EAGAIN ) ;
} else {
return AVERROR ( EIO ) ;
}
}
2010-02-18 18:27:18 +02:00
rt - > bytes_read + = ret ;
if ( rt - > bytes_read > rt - > last_bytes_read + rt - > client_report_size ) {
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Sending bytes read report \n " ) ;
2012-05-23 23:45:03 +03:00
if ( ( ret = gen_bytes_read ( s , rt , rpkt . timestamp + 1 ) ) < 0 )
return ret ;
2010-02-18 18:27:18 +02:00
rt - > last_bytes_read = rt - > bytes_read ;
}
2009-07-31 09:49:36 +03:00
ret = rtmp_parse_result ( s , rt , & rpkt ) ;
if ( ret < 0 ) { //serious error in current packet
ff_rtmp_packet_destroy ( & rpkt ) ;
2012-05-23 23:45:03 +03:00
return ret ;
2009-07-31 09:49:36 +03:00
}
2009-12-11 13:37:21 +02:00
if ( rt - > state = = STATE_STOPPED ) {
ff_rtmp_packet_destroy ( & rpkt ) ;
return AVERROR_EOF ;
}
2009-12-04 18:52:16 +02:00
if ( for_header & & ( rt - > state = = STATE_PLAYING | | rt - > state = = STATE_PUBLISHING ) ) {
2009-07-31 09:49:36 +03:00
ff_rtmp_packet_destroy ( & rpkt ) ;
return 0 ;
}
2009-12-04 18:52:16 +02:00
if ( ! rpkt . data_size | | ! rt - > is_input ) {
2009-07-31 09:49:36 +03:00
ff_rtmp_packet_destroy ( & rpkt ) ;
continue ;
}
if ( rpkt . type = = RTMP_PT_VIDEO | | rpkt . type = = RTMP_PT_AUDIO | |
2009-11-22 10:40:55 +02:00
( rpkt . type = = RTMP_PT_NOTIFY & & ! memcmp ( " \002 \000 \012 onMetaData " , rpkt . data , 13 ) ) ) {
2010-01-12 09:10:47 +02:00
ts = rpkt . timestamp ;
2009-07-31 09:49:36 +03: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 09:10:47 +02: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 10:01:04 +03:00
cts | = bytestream_get_byte ( & next ) < < 24 ;
2010-01-12 09:10:47 +02: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 09:49:36 +03: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 23:45:03 +03:00
int ret = 0 ;
2009-07-31 09:49:36 +03:00
2009-12-06 09:03:46 +02:00
if ( ! rt - > is_input ) {
2009-12-04 18:52:16 +02:00
rt - > flv_data = NULL ;
if ( rt - > out_pkt . data_size )
ff_rtmp_packet_destroy ( & rt - > out_pkt ) ;
2009-12-06 09:01:37 +02:00
if ( rt - > state > STATE_FCPUBLISH )
2012-05-23 23:45:03 +03:00
ret = gen_fcunpublish_stream ( h , rt ) ;
2009-12-04 18:52:16 +02:00
}
2009-12-06 09:01:37 +02:00
if ( rt - > state > STATE_HANDSHAKED )
2012-05-23 23:45:03 +03:00
ret = gen_delete_stream ( h , rt ) ;
2009-12-04 18:52:16 +02:00
2012-08-08 15:36:39 +03:00
free_tracked_methods ( rt ) ;
2009-07-31 09:49:36 +03:00
av_freep ( & rt - > flv_data ) ;
2011-03-31 18:36:06 +03:00
ffurl_close ( rt - > stream ) ;
2012-05-23 23:45:03 +03:00
return ret ;
2009-07-31 09:49:36 +03:00
}
/**
2010-06-30 18:38:06 +03:00
* Open RTMP connection and verify that the stream can be played .
2009-07-31 09:49:36 +03: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 12:44:21 +03:00
RTMPContext * rt = s - > priv_data ;
2009-12-03 17:47:00 +02:00
char proto [ 8 ] , hostname [ 256 ] , path [ 1024 ] , * fname ;
2012-04-15 22:49:48 +03:00
char * old_app ;
2009-07-31 09:49:36 +03:00
uint8_t buf [ 2048 ] ;
2009-12-02 14:55:10 +02:00
int port ;
2012-07-17 13:02:43 +03:00
AVDictionary * opts = NULL ;
2009-07-31 09:49:36 +03:00
int ret ;
2011-04-15 17:42:09 +03:00
rt - > is_input = ! ( flags & AVIO_FLAG_WRITE ) ;
2009-07-31 09:49:36 +03:00
2010-06-27 17:16:46 +03:00
av_url_split ( proto , sizeof ( proto ) , NULL , 0 , hostname , sizeof ( hostname ) , & port ,
2010-03-08 11:05:03 +02:00
path , sizeof ( path ) , s - > filename ) ;
2009-07-31 09:49:36 +03:00
2012-07-17 13:02:43 +03:00
if ( ! strcmp ( proto , " rtmpt " ) | | ! strcmp ( proto , " rtmpts " ) ) {
if ( ! strcmp ( proto , " rtmpts " ) )
av_dict_set ( & opts , " ffrtmphttp_tls " , " 1 " , 1 ) ;
2012-06-17 21:24:43 +03:00
/* open the http tunneling connection */
2012-07-16 12:56:20 +03:00
ff_url_join ( buf , sizeof ( buf ) , " ffrtmphttp " , NULL , hostname , port , NULL ) ;
2012-07-17 13:02:42 +03: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 17:36:47 +03:00
} else if ( ! strcmp ( proto , " rtmpe " ) | | ( ! strcmp ( proto , " rtmpte " ) ) ) {
if ( ! strcmp ( proto , " rtmpte " ) )
av_dict_set ( & opts , " ffrtmpcrypt_tunneling " , " 1 " , 1 ) ;
2012-07-19 15:13:58 +03:00
/* open the encrypted connection */
ff_url_join ( buf , sizeof ( buf ) , " ffrtmpcrypt " , NULL , hostname , port , NULL ) ;
rt - > encrypted = 1 ;
2012-06-17 21:24:43 +03: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 09:49:36 +03:00
2012-05-23 23:45:03 +03:00
if ( ( ret = ffurl_open ( & rt - > stream , buf , AVIO_FLAG_READ_WRITE ,
2012-07-17 13:02:43 +03:00
& s - > interrupt_callback , & opts ) ) < 0 ) {
2011-04-15 17:42:09 +03:00
av_log ( s , AV_LOG_ERROR , " Cannot open connection %s \n " , buf ) ;
2009-07-31 09:49:36 +03:00
goto fail ;
2009-11-22 10:42:55 +02:00
}
2009-07-31 09:49:36 +03:00
2009-12-04 18:52:42 +02:00
rt - > state = STATE_START ;
2012-05-23 23:45:03 +03:00
if ( ( ret = rtmp_handshake ( s , rt ) ) < 0 )
2011-12-01 12:34:06 +03:00
goto fail ;
2009-07-31 09:49:36 +03:00
2009-12-04 18:52:42 +02:00
rt - > chunk_size = 128 ;
rt - > state = STATE_HANDSHAKED ;
2012-04-15 22:49:48 +03: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 23:45:03 +03:00
ret = AVERROR ( ENOMEM ) ;
goto fail ;
2012-04-15 22:49:48 +03:00
}
2009-12-04 18:52:42 +02:00
//extract "app" part from path
if ( ! strncmp ( path , " /ondemand/ " , 10 ) ) {
fname = path + 10 ;
memcpy ( rt - > app , " ondemand " , 9 ) ;
} else {
2012-05-16 11:45:47 +03:00
char * next = * path ? path + 1 : path ;
char * p = strchr ( next , ' / ' ) ;
2009-12-04 18:52:42 +02:00
if ( ! p ) {
2012-05-16 11:45:47 +03:00
fname = next ;
2009-12-04 18:52:42 +02:00
rt - > app [ 0 ] = ' \0 ' ;
2009-07-31 09:49:36 +03:00
} else {
2012-05-15 03:24:27 +03:00
// make sure we do not mismatch a playpath for an application instance
2009-12-04 18:52:42 +02:00
char * c = strchr ( p + 1 , ' : ' ) ;
fname = strchr ( p + 1 , ' / ' ) ;
2012-05-15 03:24:27 +03:00
if ( ! fname | | ( c & & c < fname ) ) {
2009-12-04 18:52:42 +02:00
fname = p + 1 ;
av_strlcpy ( rt - > app , path + 1 , p - path ) ;
2009-07-31 09:49:36 +03:00
} else {
2009-12-04 18:52:42 +02:00
fname + + ;
av_strlcpy ( rt - > app , path + 1 , fname - path - 1 ) ;
2009-07-31 09:49:36 +03:00
}
}
2009-12-04 18:52:42 +02:00
}
2012-04-15 22:49:48 +03: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 22:50:50 +03:00
if ( ! rt - > playpath ) {
2012-06-07 18:46:34 +03:00
int len = strlen ( fname ) ;
2012-04-15 22:50:50 +03:00
rt - > playpath = av_malloc ( PLAYPATH_MAX_LENGTH ) ;
if ( ! rt - > playpath ) {
2012-05-23 23:45:03 +03:00
ret = AVERROR ( ENOMEM ) ;
goto fail ;
2012-04-15 22:50:50 +03:00
}
2012-06-11 15:21:32 +03:00
if ( ! strchr ( fname , ' : ' ) & & len > = 4 & &
2012-06-07 18:46:34 +03:00
( ! strcmp ( fname + len - 4 , " .f4v " ) | |
! strcmp ( fname + len - 4 , " .mp4 " ) ) ) {
2012-04-15 22:50:50 +03:00
memcpy ( rt - > playpath , " mp4: " , 5 ) ;
2012-06-11 15:21:32 +03:00
} else if ( len > = 4 & & ! strcmp ( fname + len - 4 , " .flv " ) ) {
2012-06-07 18:46:34 +03:00
fname [ len - 4 ] = ' \0 ' ;
2012-04-15 22:50:50 +03:00
} else {
rt - > playpath [ 0 ] = 0 ;
}
strncat ( rt - > playpath , fname , PLAYPATH_MAX_LENGTH - 5 ) ;
2009-12-04 18:52:42 +02:00
}
2009-07-31 09:49:36 +03:00
2012-05-09 03:12:14 +03:00
if ( ! rt - > tcurl ) {
rt - > tcurl = av_malloc ( TCURL_MAX_LENGTH ) ;
2012-05-23 19:55:34 +03:00
if ( ! rt - > tcurl ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2012-05-09 03:12:14 +03:00
ff_url_join ( rt - > tcurl , TCURL_MAX_LENGTH , proto , NULL , hostname ,
port , " /%s " , rt - > app ) ;
}
2012-05-09 03:12:15 +03:00
if ( ! rt - > flashver ) {
rt - > flashver = av_malloc ( FLASHVER_MAX_LENGTH ) ;
2012-05-23 19:55:34 +03:00
if ( ! rt - > flashver ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2012-05-09 03:12:15 +03: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 18:27:18 +02:00
rt - > client_report_size = 1048576 ;
rt - > bytes_read = 0 ;
rt - > last_bytes_read = 0 ;
2012-06-13 15:47:26 +03:00
rt - > server_bw = 2500000 ;
2010-02-18 18:27:18 +02:00
2011-02-03 13:53:28 +02:00
av_log ( s , AV_LOG_DEBUG , " Proto = %s, path = %s, app = %s, fname = %s \n " ,
2009-12-04 18:52:42 +02:00
proto , path , rt - > app , rt - > playpath ) ;
2012-05-23 23:45:03 +03:00
if ( ( ret = gen_connect ( s , rt ) ) < 0 )
goto fail ;
2009-07-31 09:49:36 +03:00
2009-12-04 18:52:42 +02:00
do {
ret = get_packet ( s , 1 ) ;
} while ( ret = = EAGAIN ) ;
if ( ret < 0 )
goto fail ;
2009-12-04 18:52:16 +02:00
if ( rt - > is_input ) {
2009-07-31 09:49:36 +03: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 18:52:16 +02:00
} else {
rt - > flv_size = 0 ;
rt - > flv_data = NULL ;
rt - > flv_off = 0 ;
2011-09-21 23:21:30 +03:00
rt - > skip_bytes = 13 ;
2009-07-31 09:49:36 +03:00
}
2011-03-31 18:58:04 +03:00
s - > max_packet_size = rt - > stream - > max_packet_size ;
2009-07-31 09:49:36 +03:00
s - > is_streamed = 1 ;
return 0 ;
fail :
2012-07-17 13:02:43 +03:00
av_dict_free ( & opts ) ;
2009-07-31 09:49:36 +03:00
rtmp_close ( s ) ;
2012-05-23 23:45:03 +03:00
return ret ;
2009-07-31 09:49:36 +03: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 15:02:51 +03:00
return data_left ;
2009-07-31 09:49:36 +03:00
}
if ( ( ret = get_packet ( s , 0 ) ) < 0 )
return ret ;
}
return orig_size ;
}
2011-02-03 13:17:35 +02:00
static int rtmp_write ( URLContext * s , const uint8_t * buf , int size )
2009-07-31 09:49:36 +03:00
{
2011-02-03 13:17:35 +02:00
RTMPContext * rt = s - > priv_data ;
2009-12-04 18:52:16 +02:00
int size_temp = size ;
int pktsize , pkttype ;
uint32_t ts ;
const uint8_t * buf_temp = buf ;
2012-06-14 16:28:40 +03:00
uint8_t c ;
2012-05-23 23:45:03 +03:00
int ret ;
2009-12-04 18:52:16 +02:00
do {
2011-09-21 23:21:30 +03: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 18:52:16 +02:00
2011-09-21 23:21:30 +03: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 18:52:16 +02: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 23:45:03 +03:00
if ( ( ret = ff_rtmp_packet_create ( & rt - > out_pkt , RTMP_SOURCE_CHANNEL ,
pkttype , ts , pktsize ) ) < 0 )
return ret ;
2009-12-04 18:52:16 +02: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 06:39:53 +03:00
size_temp = 0 ;
2009-12-04 18:52:16 +02:00
} else {
bytestream_get_buffer ( & buf_temp , rt - > flv_data + rt - > flv_off , rt - > flv_size - rt - > flv_off ) ;
2011-09-18 06:39:53 +03:00
size_temp - = rt - > flv_size - rt - > flv_off ;
2009-12-04 18:52:16 +02:00
rt - > flv_off + = rt - > flv_size - rt - > flv_off ;
}
if ( rt - > flv_off = = rt - > flv_size ) {
2011-09-21 23:21:30 +03:00
rt - > skip_bytes = 4 ;
2012-08-08 15:36:39 +03:00
if ( ( ret = rtmp_send_packet ( rt , & rt - > out_pkt , 0 ) ) < 0 )
2012-05-24 14:48:25 +03:00
return ret ;
2009-12-04 18:52:16 +02:00
rt - > flv_size = 0 ;
rt - > flv_off = 0 ;
2011-09-21 23:21:30 +03:00
rt - > flv_header_bytes = 0 ;
2012-06-18 15:55:55 +03:00
rt - > flv_nb_packets + + ;
2009-12-04 18:52:16 +02:00
}
2011-09-18 06:39:53 +03:00
} while ( buf_temp - buf < size ) ;
2012-06-14 16:28:40 +03:00
2012-06-18 15:55:55 +03:00
if ( rt - > flv_nb_packets < rt - > flush_interval )
return size ;
rt - > flv_nb_packets = 0 ;
2012-06-14 16:28:40 +03: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 18:52:16 +02:00
return size ;
2009-07-31 09:49:36 +03:00
}
2012-04-15 22:49:48 +03: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 16:02:03 +03: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 14:16:34 +03: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 03:12:15 +03: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 15:55:55 +03: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 20:33:26 +03: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 17:29:40 +03: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 22:50:50 +03:00
{ " rtmp_playpath " , " Stream identifier to play or to publish " , OFFSET ( playpath ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC | ENC } ,
2012-08-07 23:02:27 +03:00
{ " rtmp_subscribe " , " Name of live stream to subscribe to. Defaults to rtmp_playpath. " , OFFSET ( subscribe ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC } ,
2012-05-09 03:12:16 +03: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 17:29:38 +03: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 22:49:48 +03:00
{ NULL } ,
} ;
2012-08-07 13:21:31 +03:00
# define RTMP_PROTOCOL(flavor) \
static const AVClass flavor # # _class = { \
. class_name = # flavor , \
. item_name = av_default_item_name , \
. option = rtmp_options , \
. version = LIBAVUTIL_VERSION_INT , \
} ; \
\
URLProtocol ff_ # # flavor # # _protocol = { \
. name = # flavor , \
. 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 = & flavor # # _class , \
2012-04-15 22:49:48 +03:00
} ;
2012-07-20 17:36:47 +03:00
2012-08-07 13:21:31 +03:00
RTMP_PROTOCOL ( rtmp )
RTMP_PROTOCOL ( rtmpe )
RTMP_PROTOCOL ( rtmps )
RTMP_PROTOCOL ( rtmpt )
RTMP_PROTOCOL ( rtmpte )
RTMP_PROTOCOL ( rtmpts )