@ -29,6 +29,7 @@
# include "libavutil/intfloat.h"
# include "libavutil/lfg.h"
# include "libavutil/opt.h"
# include "libavutil/random_seed.h"
# include "libavutil/sha.h"
# include "avformat.h"
# include "internal.h"
@ -51,6 +52,7 @@
# define PLAYPATH_MAX_LENGTH 256
# define TCURL_MAX_LENGTH 512
# define FLASHVER_MAX_LENGTH 64
# define RTMP_PKTDATA_DEFAULT_SIZE 4096
/** RTMP protocol handler state */
typedef enum {
@ -59,6 +61,7 @@ typedef enum {
STATE_FCPUBLISH , ///< client FCPublishing stream (for output)
STATE_PLAYING , ///< client has started receiving multimedia data from server
STATE_PUBLISHING , ///< client has started sending multimedia data to server (for output)
STATE_RECEIVING , ///< received a publish command (for input)
STATE_STOPPED , ///< the broadcast has been stopped
} ClientState ;
@ -110,6 +113,9 @@ typedef struct RTMPContext {
TrackedMethod * tracked_methods ; ///< tracked methods buffer
int nb_tracked_methods ; ///< number of tracked methods
int tracked_methods_size ; ///< size of the tracked methods buffer
int listen ; ///< listen mode flag
int listen_timeout ; ///< listen timeout to wait for new connections
int nb_streamid ; ///< The next stream id to return on createStream calls
} RTMPContext ;
# define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing
@ -377,6 +383,151 @@ static int gen_connect(URLContext *s, RTMPContext *rt)
return rtmp_send_packet ( rt , & pkt , 1 ) ;
}
static int read_connect ( URLContext * s , RTMPContext * rt )
{
RTMPPacket pkt = { 0 } ;
uint8_t * p ;
const uint8_t * cp ;
int ret ;
char command [ 64 ] ;
int stringlen ;
double seqnum ;
uint8_t tmpstr [ 256 ] ;
GetByteContext gbc ;
if ( ( ret = ff_rtmp_packet_read ( rt - > stream , & pkt , rt - > in_chunk_size ,
rt - > prev_pkt [ 1 ] ) ) < 0 )
return ret ;
cp = pkt . data ;
bytestream2_init ( & gbc , cp , pkt . data_size ) ;
if ( ff_amf_read_string ( & gbc , command , sizeof ( command ) , & stringlen ) ) {
av_log ( s , AV_LOG_ERROR , " Unable to read command string \n " ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
return AVERROR_INVALIDDATA ;
}
if ( strcmp ( command , " connect " ) ) {
av_log ( s , AV_LOG_ERROR , " Expecting connect, got %s \n " , command ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
return AVERROR_INVALIDDATA ;
}
ret = ff_amf_read_number ( & gbc , & seqnum ) ;
if ( ret )
av_log ( s , AV_LOG_WARNING , " SeqNum not found \n " ) ;
/* Here one could parse an AMF Object with data as flashVers and others. */
ret = ff_amf_get_field_value ( gbc . buffer ,
gbc . buffer + bytestream2_get_bytes_left ( & gbc ) ,
" app " , tmpstr , sizeof ( tmpstr ) ) ;
if ( ret )
av_log ( s , AV_LOG_WARNING , " App field not found in connect \n " ) ;
if ( ! ret & & strcmp ( tmpstr , rt - > app ) )
av_log ( s , AV_LOG_WARNING , " App field don't match up: %s <-> %s \n " ,
tmpstr , rt - > app ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
// Send Window Acknowledgement Size (as defined in speficication)
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
RTMP_PT_SERVER_BW , 0 , 4 ) ) < 0 )
return ret ;
p = pkt . data ;
bytestream_put_be32 ( & p , rt - > server_bw ) ;
pkt . data_size = p - pkt . data ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
if ( ret < 0 )
return ret ;
// Send Peer Bandwidth
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
RTMP_PT_CLIENT_BW , 0 , 5 ) ) < 0 )
return ret ;
p = pkt . data ;
bytestream_put_be32 ( & p , rt - > server_bw ) ;
bytestream_put_byte ( & p , 2 ) ; // dynamic
pkt . data_size = p - pkt . data ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
if ( ret < 0 )
return ret ;
// Ping request
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
RTMP_PT_PING , 0 , 6 ) ) < 0 )
return ret ;
p = pkt . data ;
bytestream_put_be16 ( & p , 0 ) ; // 0 -> Stream Begin
bytestream_put_be32 ( & p , 0 ) ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
if ( ret < 0 )
return ret ;
// Chunk size
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_CHUNK_SIZE , 0 , 4 ) ) < 0 )
return ret ;
p = pkt . data ;
bytestream_put_be32 ( & p , rt - > out_chunk_size ) ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
if ( ret < 0 )
return ret ;
// Send result_ NetConnection.Connect.Success to connect
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_INVOKE , 0 ,
RTMP_PKTDATA_DEFAULT_SIZE ) ) < 0 )
return ret ;
p = pkt . data ;
ff_amf_write_string ( & p , " _result " ) ;
ff_amf_write_number ( & p , seqnum ) ;
ff_amf_write_object_start ( & p ) ;
ff_amf_write_field_name ( & p , " fmsVer " ) ;
ff_amf_write_string ( & p , " FMS/3,0,1,123 " ) ;
ff_amf_write_field_name ( & p , " capabilities " ) ;
ff_amf_write_number ( & p , 31 ) ;
ff_amf_write_object_end ( & p ) ;
ff_amf_write_object_start ( & p ) ;
ff_amf_write_field_name ( & p , " level " ) ;
ff_amf_write_string ( & p , " status " ) ;
ff_amf_write_field_name ( & p , " code " ) ;
ff_amf_write_string ( & p , " NetConnection.Connect.Success " ) ;
ff_amf_write_field_name ( & p , " description " ) ;
ff_amf_write_string ( & p , " Connection succeeded. " ) ;
ff_amf_write_field_name ( & p , " objectEncoding " ) ;
ff_amf_write_number ( & p , 0 ) ;
ff_amf_write_object_end ( & p ) ;
pkt . data_size = p - pkt . data ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
if ( ret < 0 )
return ret ;
if ( ( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_INVOKE , 0 , 30 ) ) < 0 )
return ret ;
p = pkt . data ;
ff_amf_write_string ( & p , " onBWDone " ) ;
ff_amf_write_number ( & p , 0 ) ;
ff_amf_write_null ( & p ) ;
ff_amf_write_number ( & p , 8192 ) ;
pkt . data_size = p - pkt . data ;
ret = ff_rtmp_packet_write ( rt - > stream , & pkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & pkt ) ;
return ret ;
}
/**
* Generate ' releaseStream ' call and send it to the server . It should make
* the server release some channel for media streams .
@ -1138,6 +1289,123 @@ static int rtmp_handshake(URLContext *s, RTMPContext *rt)
return 0 ;
}
static int rtmp_receive_hs_packet ( RTMPContext * rt , uint32_t * first_int ,
uint32_t * second_int , char * arraydata ,
int size )
{
int inoutsize ;
inoutsize = ffurl_read_complete ( rt - > stream , arraydata ,
RTMP_HANDSHAKE_PACKET_SIZE ) ;
if ( inoutsize < = 0 )
return AVERROR ( EIO ) ;
if ( inoutsize ! = RTMP_HANDSHAKE_PACKET_SIZE ) {
av_log ( rt , AV_LOG_ERROR , " Erroneous Message size %d "
" not following standard \n " , ( int ) inoutsize ) ;
return AVERROR ( EINVAL ) ;
}
* first_int = AV_RB32 ( arraydata ) ;
* second_int = AV_RB32 ( arraydata + 4 ) ;
return 0 ;
}
static int rtmp_send_hs_packet ( RTMPContext * rt , uint32_t first_int ,
uint32_t second_int , char * arraydata , int size )
{
int inoutsize ;
AV_WB32 ( arraydata , first_int ) ;
AV_WB32 ( arraydata + 4 , first_int ) ;
inoutsize = ffurl_write ( rt - > stream , arraydata ,
RTMP_HANDSHAKE_PACKET_SIZE ) ;
if ( inoutsize ! = RTMP_HANDSHAKE_PACKET_SIZE ) {
av_log ( rt , AV_LOG_ERROR , " Unable to write answer \n " ) ;
return AVERROR ( EIO ) ;
}
return 0 ;
}
/**
* rtmp handshake server side
*/
static int rtmp_server_handshake ( URLContext * s , RTMPContext * rt )
{
uint8_t buffer [ RTMP_HANDSHAKE_PACKET_SIZE ] ;
uint32_t hs_epoch ;
uint32_t hs_my_epoch ;
uint8_t hs_c1 [ RTMP_HANDSHAKE_PACKET_SIZE ] ;
uint8_t hs_s1 [ RTMP_HANDSHAKE_PACKET_SIZE ] ;
uint32_t zeroes ;
uint32_t temp = 0 ;
int randomidx = 0 ;
int inoutsize = 0 ;
int ret ;
inoutsize = ffurl_read_complete ( rt - > stream , buffer , 1 ) ; // Receive C0
if ( inoutsize < = 0 ) {
av_log ( s , AV_LOG_ERROR , " Unable to read handshake \n " ) ;
return AVERROR ( EIO ) ;
}
// Check Version
if ( buffer [ 0 ] ! = 3 ) {
av_log ( s , AV_LOG_ERROR , " RTMP protocol version mismatch \n " ) ;
return AVERROR ( EIO ) ;
}
if ( ffurl_write ( rt - > stream , buffer , 1 ) < = 0 ) { // Send S0
av_log ( s , AV_LOG_ERROR ,
" Unable to write answer - RTMP S0 \n " ) ;
return AVERROR ( EIO ) ;
}
/* Receive C1 */
ret = rtmp_receive_hs_packet ( rt , & hs_epoch , & zeroes , hs_c1 ,
RTMP_HANDSHAKE_PACKET_SIZE ) ;
if ( ret ) {
av_log ( s , AV_LOG_ERROR , " RTMP Handshake C1 Error \n " ) ;
return ret ;
}
if ( zeroes )
av_log ( s , AV_LOG_WARNING , " Erroneous C1 Message zero != 0 \n " ) ;
/* Send S1 */
/* By now same epoch will be sent */
hs_my_epoch = hs_epoch ;
/* Generate random */
for ( randomidx = 0 ; randomidx < ( RTMP_HANDSHAKE_PACKET_SIZE ) ;
randomidx + = 4 )
AV_WB32 ( hs_s1 + 8 + randomidx , av_get_random_seed ( ) ) ;
ret = rtmp_send_hs_packet ( rt , hs_my_epoch , 0 , hs_s1 ,
RTMP_HANDSHAKE_PACKET_SIZE ) ;
if ( ret ) {
av_log ( s , AV_LOG_ERROR , " RTMP Handshake S1 Error \n " ) ;
return ret ;
}
/* Send S2 */
ret = rtmp_send_hs_packet ( rt , hs_epoch , 0 , hs_c1 ,
RTMP_HANDSHAKE_PACKET_SIZE ) ;
if ( ret ) {
av_log ( s , AV_LOG_ERROR , " RTMP Handshake S2 Error \n " ) ;
return ret ;
}
/* Receive C2 */
ret = rtmp_receive_hs_packet ( rt , & temp , & zeroes , buffer ,
RTMP_HANDSHAKE_PACKET_SIZE ) ;
if ( ret ) {
av_log ( s , AV_LOG_ERROR , " RTMP Handshake C2 Error \n " ) ;
return ret ;
}
if ( temp ! = hs_my_epoch )
av_log ( s , AV_LOG_WARNING ,
" Erroneous C2 Message epoch does not match up with C1 epoch \n " ) ;
if ( memcmp ( buffer + 8 , hs_s1 + 8 ,
RTMP_HANDSHAKE_PACKET_SIZE - 8 ) )
av_log ( s , AV_LOG_WARNING ,
" Erroneous C2 Message random does not match up \n " ) ;
return 0 ;
}
static int handle_chunk_size ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
@ -1270,6 +1538,139 @@ static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
return ret ;
}
static int send_invoke_response ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
double seqnum ;
char filename [ 64 ] ;
char command [ 64 ] ;
char statusmsg [ 128 ] ;
int stringlen ;
char * pchar ;
const uint8_t * p = pkt - > data ;
uint8_t * pp = NULL ;
RTMPPacket spkt = { 0 } ;
GetByteContext gbc ;
int ret ;
bytestream2_init ( & gbc , p , pkt - > data_size ) ;
if ( ff_amf_read_string ( & gbc , command , sizeof ( command ) ,
& stringlen ) ) {
av_log ( s , AV_LOG_ERROR , " Error in PT_INVOKE \n " ) ;
return AVERROR_INVALIDDATA ;
}
ret = ff_amf_read_number ( & gbc , & seqnum ) ;
if ( ret )
return ret ;
ret = ff_amf_read_null ( & gbc ) ;
if ( ret )
return ret ;
if ( ! strcmp ( command , " FCPublish " ) | |
! strcmp ( command , " publish " ) ) {
ret = ff_amf_read_string ( & gbc , filename ,
sizeof ( filename ) , & stringlen ) ;
// check with url
if ( s - > filename ) {
pchar = strrchr ( s - > filename , ' / ' ) ;
if ( ! pchar ) {
av_log ( s , AV_LOG_WARNING ,
" Unable to find / in url %s, bad format \n " ,
s - > filename ) ;
pchar = s - > filename ;
}
pchar + + ;
if ( strcmp ( pchar , filename ) )
av_log ( s , AV_LOG_WARNING , " Unexpected stream %s, expecting "
" %s \n " , filename , pchar ) ;
}
rt - > state = STATE_RECEIVING ;
}
if ( ! strcmp ( command , " FCPublish " ) ) {
if ( ( ret = ff_rtmp_packet_create ( & spkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_INVOKE , 0 ,
RTMP_PKTDATA_DEFAULT_SIZE ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Unable to create response packet \n " ) ;
return ret ;
}
pp = spkt . data ;
ff_amf_write_string ( & pp , " onFCPublish " ) ;
} else if ( ! strcmp ( command , " publish " ) ) {
PutByteContext pbc ;
// Send Stream Begin 1
if ( ( ret = ff_rtmp_packet_create ( & spkt , RTMP_NETWORK_CHANNEL ,
RTMP_PT_PING , 0 , 6 ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Unable to create response packet \n " ) ;
return ret ;
}
pp = spkt . data ;
bytestream2_init_writer ( & pbc , pp , spkt . data_size ) ;
bytestream2_put_be16 ( & pbc , 0 ) ; // 0 -> Stream Begin
bytestream2_put_be32 ( & pbc , rt - > nb_streamid ) ;
ret = ff_rtmp_packet_write ( rt - > stream , & spkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & spkt ) ;
if ( ret < 0 )
return ret ;
// Send onStatus(NetStream.Publish.Start)
if ( ( ret = ff_rtmp_packet_create ( & spkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_INVOKE , 0 ,
RTMP_PKTDATA_DEFAULT_SIZE ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Unable to create response packet \n " ) ;
return ret ;
}
spkt . extra = pkt - > extra ;
pp = spkt . data ;
ff_amf_write_string ( & pp , " onStatus " ) ;
ff_amf_write_number ( & pp , 0 ) ;
ff_amf_write_null ( & pp ) ;
ff_amf_write_object_start ( & pp ) ;
ff_amf_write_field_name ( & pp , " level " ) ;
ff_amf_write_string ( & pp , " status " ) ;
ff_amf_write_field_name ( & pp , " code " ) ;
ff_amf_write_string ( & pp , " NetStream.Publish.Start " ) ;
ff_amf_write_field_name ( & pp , " description " ) ;
snprintf ( statusmsg , sizeof ( statusmsg ) ,
" %s is now published " , filename ) ;
ff_amf_write_string ( & pp , statusmsg ) ;
ff_amf_write_field_name ( & pp , " details " ) ;
ff_amf_write_string ( & pp , filename ) ;
ff_amf_write_field_name ( & pp , " clientid " ) ;
snprintf ( statusmsg , sizeof ( statusmsg ) , " %s " , LIBAVFORMAT_IDENT ) ;
ff_amf_write_string ( & pp , statusmsg ) ;
ff_amf_write_object_end ( & pp ) ;
} else {
if ( ( ret = ff_rtmp_packet_create ( & spkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_INVOKE , 0 ,
RTMP_PKTDATA_DEFAULT_SIZE ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Unable to create response packet \n " ) ;
return ret ;
}
pp = spkt . data ;
ff_amf_write_string ( & pp , " _result " ) ;
ff_amf_write_number ( & pp , seqnum ) ;
ff_amf_write_null ( & pp ) ;
if ( ! strcmp ( command , " createStream " ) ) {
rt - > nb_streamid + + ;
if ( rt - > nb_streamid = = 0 | | rt - > nb_streamid = = 2 )
rt - > nb_streamid + + ; /* Values 0 and 2 are reserved */
ff_amf_write_number ( & pp , rt - > nb_streamid ) ;
/* By now we don't control which streams are removed in
* deleteStream . There is no stream creation control
* if a client creates more than 2 ^ 32 - 2 streams . */
}
}
spkt . data_size = pp - spkt . data ;
ret = ff_rtmp_packet_write ( rt - > stream , & spkt , rt - > out_chunk_size ,
rt - > prev_pkt [ 1 ] ) ;
ff_rtmp_packet_destroy ( & spkt ) ;
return ret ;
}
static int handle_invoke_result ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s - > priv_data ;
@ -1384,11 +1785,79 @@ static int handle_invoke(URLContext *s, RTMPPacket *pkt)
} else if ( ! memcmp ( pkt - > data , " \002 \000 \010 onBWDone " , 11 ) ) {
if ( ( ret = gen_check_bw ( s , rt ) ) < 0 )
return ret ;
} else if ( ! memcmp ( pkt - > data , " \002 \000 \015 releaseStream " , 16 ) | |
! memcmp ( pkt - > data , " \002 \000 \011 FCPublish " , 12 ) | |
! memcmp ( pkt - > data , " \002 \000 \007 publish " , 10 ) | |
! memcmp ( pkt - > data , " \002 \000 \010 _checkbw " , 11 ) | |
! memcmp ( pkt - > data , " \002 \000 \014 createStream " , 15 ) ) {
if ( ret = send_invoke_response ( s , pkt ) < 0 )
return ret ;
}
return ret ;
}
static int handle_notify ( URLContext * s , RTMPPacket * pkt ) {
RTMPContext * rt = s - > priv_data ;
const uint8_t * p = NULL ;
uint8_t * cp = NULL ;
uint8_t commandbuffer [ 64 ] ;
char statusmsg [ 128 ] ;
int stringlen ;
GetByteContext gbc ;
PutByteContext pbc ;
uint32_t ts ;
int old_flv_size ;
const uint8_t * datatowrite ;
unsigned datatowritelength ;
p = pkt - > data ;
bytestream2_init ( & gbc , p , pkt - > data_size ) ;
if ( ff_amf_read_string ( & gbc , commandbuffer , sizeof ( commandbuffer ) ,
& stringlen ) )
return AVERROR_INVALIDDATA ;
if ( ! strcmp ( commandbuffer , " @setDataFrame " ) ) {
datatowrite = gbc . buffer ;
datatowritelength = bytestream2_get_bytes_left ( & gbc ) ;
if ( ff_amf_read_string ( & gbc , statusmsg ,
sizeof ( statusmsg ) , & stringlen ) )
return AVERROR_INVALIDDATA ;
if ( strcmp ( statusmsg , " onMetaData " ) ) {
av_log ( s , AV_LOG_INFO , " Expecting onMetadata but got %s \n " ,
statusmsg ) ;
return 0 ;
}
/* Provide ECMAArray to flv */
ts = pkt - > timestamp ;
// generate packet header and put data into buffer for FLV demuxer
if ( rt - > flv_off < rt - > flv_size ) {
old_flv_size = rt - > flv_size ;
rt - > flv_size + = datatowritelength + 15 ;
} else {
old_flv_size = 0 ;
rt - > flv_size = datatowritelength + 15 ;
rt - > flv_off = 0 ;
}
cp = av_realloc ( rt - > flv_data , rt - > flv_size ) ;
if ( ! cp )
return AVERROR ( ENOMEM ) ;
rt - > flv_data = cp ;
bytestream2_init_writer ( & pbc , cp , rt - > flv_size ) ;
bytestream2_skip_p ( & pbc , old_flv_size ) ;
bytestream2_put_byte ( & pbc , pkt - > type ) ;
bytestream2_put_be24 ( & pbc , datatowritelength ) ;
bytestream2_put_be24 ( & pbc , ts ) ;
bytestream2_put_byte ( & pbc , ts > > 24 ) ;
bytestream2_put_be24 ( & pbc , 0 ) ;
bytestream2_put_buffer ( & pbc , datatowrite , datatowritelength ) ;
bytestream2_put_be32 ( & pbc , 0 ) ;
}
return 0 ;
}
/**
* Parse received packet and possibly perform some action depending on
* the packet contents .
@ -1430,6 +1899,7 @@ static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt)
case RTMP_PT_VIDEO :
case RTMP_PT_AUDIO :
case RTMP_PT_METADATA :
case RTMP_PT_NOTIFY :
/* Audio, Video and Metadata packets are parsed in get_packet() */
break ;
default :
@ -1489,7 +1959,9 @@ static int get_packet(URLContext *s, int for_header)
ff_rtmp_packet_destroy ( & rpkt ) ;
return AVERROR_EOF ;
}
if ( for_header & & ( rt - > state = = STATE_PLAYING | | rt - > state = = STATE_PUBLISHING ) ) {
if ( for_header & & ( rt - > state = = STATE_PLAYING | |
rt - > state = = STATE_PUBLISHING | |
rt - > state = = STATE_RECEIVING ) ) {
ff_rtmp_packet_destroy ( & rpkt ) ;
return 0 ;
}
@ -1514,6 +1986,14 @@ static int get_packet(URLContext *s, int for_header)
bytestream_put_be32 ( & p , 0 ) ;
ff_rtmp_packet_destroy ( & rpkt ) ;
return 0 ;
} else if ( rpkt . type = = RTMP_PT_NOTIFY ) {
ret = handle_notify ( s , & rpkt ) ;
ff_rtmp_packet_destroy ( & rpkt ) ;
if ( ret ) {
av_log ( s , AV_LOG_ERROR , " Handle notify error \n " ) ;
return ret ;
}
return 0 ;
} else if ( rpkt . type = = RTMP_PT_METADATA ) {
// we got raw FLV data, make it available for FLV demuxer
rt - > flv_off = 0 ;
@ -1584,11 +2064,19 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
AVDictionary * opts = NULL ;
int ret ;
if ( rt - > listen_timeout > 0 )
rt - > listen = 1 ;
rt - > is_input = ! ( flags & AVIO_FLAG_WRITE ) ;
av_url_split ( proto , sizeof ( proto ) , NULL , 0 , hostname , sizeof ( hostname ) , & port ,
path , sizeof ( path ) , s - > filename ) ;
if ( rt - > listen & & strcmp ( proto , " rtmp " ) ) {
av_log ( s , AV_LOG_ERROR , " rtmp_listen not available for %s \n " ,
proto ) ;
return AVERROR ( EINVAL ) ;
}
if ( ! strcmp ( proto , " rtmpt " ) | | ! strcmp ( proto , " rtmpts " ) ) {
if ( ! strcmp ( proto , " rtmpts " ) )
av_dict_set ( & opts , " ffrtmphttp_tls " , " 1 " , 1 ) ;
@ -1611,7 +2099,12 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
/* open the tcp connection */
if ( port < 0 )
port = RTMP_DEFAULT_PORT ;
ff_url_join ( buf , sizeof ( buf ) , " tcp " , NULL , hostname , port , NULL ) ;
if ( rt - > listen )
ff_url_join ( buf , sizeof ( buf ) , " tcp " , NULL , hostname , port ,
" ?listen&listen_timeout=%d " ,
rt - > listen_timeout * 1000 ) ;
else
ff_url_join ( buf , sizeof ( buf ) , " tcp " , NULL , hostname , port , NULL ) ;
}
if ( ( ret = ffurl_open ( & rt - > stream , buf , AVIO_FLAG_READ_WRITE ,
@ -1626,7 +2119,9 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
}
rt - > state = STATE_START ;
if ( ( ret = rtmp_handshake ( s , rt ) ) < 0 )
if ( ! rt - > listen & & ( ret = rtmp_handshake ( s , rt ) ) < 0 )
goto fail ;
if ( rt - > listen & & ( ret = rtmp_server_handshake ( s , rt ) ) < 0 )
goto fail ;
rt - > out_chunk_size = 128 ;
@ -1726,8 +2221,14 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
av_log ( s , AV_LOG_DEBUG , " Proto = %s, path = %s, app = %s, fname = %s \n " ,
proto , path , rt - > app , rt - > playpath ) ;
if ( ( ret = gen_connect ( s , rt ) ) < 0 )
goto fail ;
if ( ! rt - > listen ) {
if ( ( ret = gen_connect ( s , rt ) ) < 0 )
goto fail ;
} else {
if ( read_connect ( s , s - > priv_data ) < 0 )
goto fail ;
rt - > is_input = 1 ;
}
do {
ret = get_packet ( s , 1 ) ;
@ -1919,6 +2420,8 @@ static const AVOption rtmp_options[] = {
{ " 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 } ,
{ " rtmp_swfverify " , " URL to player swf file, compute hash/size automatically. " , OFFSET ( swfverify ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , DEC } ,
{ " 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 } ,
{ " rtmp_listen " , " Listen for incoming rtmp connections " , OFFSET ( listen ) , AV_OPT_TYPE_INT , { 0 } , INT_MIN , INT_MAX , DEC , " rtmp_listen " } ,
{ " timeout " , " Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1 " , OFFSET ( listen_timeout ) , AV_OPT_TYPE_INT , { - 1 } , INT_MIN , INT_MAX , DEC , " rtmp_listen " } ,
{ NULL } ,
} ;