2009-07-31 06:49:36 +00:00
/*
* RTMP network protocol
2013-06-01 10:38:56 +02:00
* Copyright (c) 2009 Konstantin Shishkov
2009-07-31 06:49:36 +00:00
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* 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.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
2010-04-20 14:45:34 +00:00
* @file
2009-07-31 06:49:36 +00:00
* RTMP protocol
*/
2022-02-23 14:56:49 +02:00
#include "config_components.h"
2009-07-31 06:49:36 +00:00
#include "libavcodec/bytestream.h"
#include "libavutil/avstring.h"
2012-12-30 22:39:38 +02:00
#include "libavutil/base64.h"
2011-11-27 14:04:16 +00:00
#include "libavutil/intfloat.h"
2009-07-31 06:49:36 +00:00
#include "libavutil/lfg.h"
2012-12-30 22:39:38 +02:00
#include "libavutil/md5.h"
2024-03-25 01:30:37 +01:00
#include "libavutil/mem.h"
2012-04-15 21:49:48 +02:00
#include "libavutil/opt.h"
2012-08-16 16:04:48 +02:00
#include "libavutil/random_seed.h"
2009-07-31 06:49:36 +00:00
#include "avformat.h"
2010-03-14 23:59:48 +00:00
#include "internal.h"
2009-07-31 06:49:36 +00:00
#include "network.h"
#include "flv.h"
#include "rtmp.h"
2012-07-19 14:13:58 +02:00
#include "rtmpcrypt.h"
2009-07-31 06:49:36 +00:00
#include "rtmppkt.h"
2011-03-31 16:25:10 +02:00
#include "url.h"
2022-02-23 14:18:39 +02:00
#include "version.h"
2009-07-31 06:49:36 +00:00
2012-08-15 16:11:50 +02:00
#if CONFIG_ZLIB
#include <zlib.h>
#endif
2013-02-23 17:24:15 +01:00
#define APP_MAX_LENGTH 1024
2015-07-01 16:26:53 +00:00
#define TCURL_MAX_LENGTH 1024
2012-05-09 02:12:15 +02:00
#define FLASHVER_MAX_LENGTH 64
2012-08-16 16:04:48 +02:00
#define RTMP_PKTDATA_DEFAULT_SIZE 4096
2013-09-21 21:16:04 +02:00
#define RTMP_HEADER 11
2012-04-15 21:49:48 +02:00
2009-07-31 06:49:36 +00:00
/** RTMP protocol handler state */
typedef enum {
STATE_START , ///< client has not done anything yet
STATE_HANDSHAKED , ///< client has performed handshake
2009-12-04 16:52:16 +00:00
STATE_FCPUBLISH , ///< client FCPublishing stream (for output)
2009-07-31 06:49:36 +00:00
STATE_PLAYING , ///< client has started receiving multimedia data from server
2013-08-02 12:29:23 +03:00
STATE_SEEKING , ///< client has started the seek operation. Back on STATE_PLAYING when the time comes
2009-12-04 16:52:16 +00:00
STATE_PUBLISHING , ///< client has started sending multimedia data to server (for output)
2012-08-16 16:04:48 +02:00
STATE_RECEIVING , ///< received a publish command (for input)
2013-09-15 16:52:33 +02:00
STATE_SENDING , ///< received a play command (for output)
2009-12-11 11:37:21 +00:00
STATE_STOPPED , ///< the broadcast has been stopped
2009-07-31 06:49:36 +00:00
} ClientState ;
2012-08-08 14:36:39 +02:00
typedef struct TrackedMethod {
char * name ;
int id ;
} TrackedMethod ;
2009-07-31 06:49:36 +00:00
/** protocol handler context */
typedef struct RTMPContext {
2012-04-15 21:49:48 +02:00
const AVClass * class ;
2009-07-31 06:49:36 +00:00
URLContext * stream ; ///< TCP stream used in interactions with RTMP server
2013-10-11 22:16:04 +03:00
RTMPPacket * prev_pkt [ 2 ]; ///< packet history used when reading and sending packets ([0] for reading, [1] for writing)
int nb_prev_pkt [ 2 ]; ///< number of elements in prev_pkt
2012-08-10 19:03:22 +02:00
int in_chunk_size ; ///< size of the chunks incoming RTMP packets are divided into
int out_chunk_size ; ///< size of the chunks outgoing RTMP packets are divided into
2009-12-02 12:55:10 +00: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 06:49:36 +00:00
ClientState state ; ///< current state
2013-09-16 13:20:58 -07:00
int stream_id ; ///< ID assigned by the server for the stream
2009-07-31 06:49:36 +00:00
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 16:52:16 +00:00
RTMPPacket out_pkt ; ///< rtmp packet, created from flv a/v or metadata (for output)
2017-01-31 16:15:56 +02:00
uint32_t receive_report_size ; ///< number of bytes after which we should report the number of received bytes to the peer
2017-01-14 19:17:09 +01:00
uint64_t bytes_read ; ///< number of bytes read from server
uint64_t last_bytes_read ; ///< number of bytes read last reported to server
2014-10-17 16:30:46 +02:00
uint32_t last_timestamp ; ///< last timestamp received in a packet
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
2014-05-31 20:37:26 +01:00
int has_audio ; ///< presence of audio data
int has_video ; ///< presence of video data
int received_metadata ; ///< Indicates if we have received metadata about the streams
2013-09-21 21:16:04 +02:00
uint8_t flv_header [ RTMP_HEADER ]; ///< partial incoming flv packet header
2011-09-21 23:21:30 +03:00
int flv_header_bytes ; ///< number of initialized bytes in flv_header
2011-11-12 13:28:58 -08:00
int nb_invokes ; ///< keeps track of invoke messages
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-08-13 17:05:00 +02:00
char * swfhash ; ///< SHA256 hash of the decompressed SWF file (32 bytes)
int swfhash_len ; ///< length of the SHA256 hash
int swfsize ; ///< size of the decompressed SWF file
2012-05-09 02:12:16 +02:00
char * swfurl ; ///< url of the swf player
2012-08-15 16:11:50 +02:00
char * swfverify ; ///< URL to player swf file, compute hash/size automatically
2012-08-13 17:05:00 +02:00
char swfverification [ 42 ]; ///< hash of the SWF verification
2012-07-24 16:29:40 +02:00
char * pageurl ; ///< url of the web page
2012-08-07 22:02:27 +02:00
char * subscribe ; ///< name of live stream to subscribe
2017-01-31 16:15:56 +02:00
int max_sent_unacked ; ///< max unacked sent bytes
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)
2012-08-08 14:36:39 +02: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
2012-08-16 16:04:48 +02:00
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
2014-10-14 17:16:21 +02:00
double duration ; ///< Duration of the stream in seconds as returned by the server (only valid if non-zero)
2021-06-11 21:02:02 +02:00
int tcp_nodelay ; ///< Use TCP_NODELAY to disable Nagle's algorithm if set to 1
2023-08-28 09:59:24 +08:00
char * enhanced_codecs ; ///< codec list in enhanced rtmp
2012-12-30 22:39:38 +02:00
char username [ 50 ];
char password [ 50 ];
char auth_params [ 500 ];
int do_reconnect ;
int auth_tried ;
2009-07-31 06:49:36 +00: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
};
2014-04-13 01:06:22 +02:00
static int handle_chunk_size ( URLContext * s , RTMPPacket * pkt );
2017-09-26 18:08:25 -03:00
static int handle_window_ack_size ( URLContext * s , RTMPPacket * pkt );
static int handle_set_peer_bw ( URLContext * s , RTMPPacket * pkt );
2014-04-13 01:06:22 +02:00
2025-10-30 23:20:41 +01:00
static size_t zstrlen ( const char * c )
{
if ( c )
return strlen ( c );
return 0 ;
}
2012-08-08 14:36:39 +02:00
static int add_tracked_method ( RTMPContext * rt , const char * name , int id )
{
2013-09-18 18:12:36 +02:00
int err ;
2012-08-08 14:36:39 +02:00
if ( rt -> nb_tracked_methods + 1 > rt -> tracked_methods_size ) {
rt -> tracked_methods_size = ( rt -> nb_tracked_methods + 1 ) * 2 ;
2020-04-26 17:49:22 +08:00
if (( err = av_reallocp_array ( & rt -> tracked_methods , rt -> tracked_methods_size ,
2013-09-26 16:37:02 +03:00
sizeof ( * rt -> tracked_methods ))) < 0 ) {
rt -> nb_tracked_methods = 0 ;
rt -> tracked_methods_size = 0 ;
2013-09-18 18:12:36 +02:00
return err ;
2013-09-26 16:37:02 +03:00
}
2012-08-08 14:36:39 +02:00
}
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 -- ;
}
2012-08-11 12:41:32 +02:00
static int find_tracked_method ( URLContext * s , RTMPPacket * pkt , int offset ,
char ** tracked_method )
{
RTMPContext * rt = s -> priv_data ;
GetByteContext gbc ;
double pkt_id ;
int ret ;
int i ;
2013-08-08 18:52:11 +02:00
bytestream2_init ( & gbc , pkt -> data + offset , pkt -> size - offset );
2012-08-11 12:41:32 +02:00
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 ;
}
return 0 ;
}
2012-08-08 14:36:39 +02:00
static void free_tracked_methods ( RTMPContext * rt )
{
int i ;
for ( i = 0 ; i < rt -> nb_tracked_methods ; i ++ )
2014-12-22 11:52:22 +01:00
av_freep ( & rt -> tracked_methods [ i ]. name );
av_freep ( & rt -> tracked_methods );
2012-12-30 22:39:38 +02:00
rt -> tracked_methods_size = 0 ;
rt -> nb_tracked_methods = 0 ;
2012-08-08 14:36:39 +02:00
}
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 ;
2013-08-08 18:52:11 +02:00
bytestream2_init ( & gbc , pkt -> data , pkt -> size );
2012-08-08 14:36:39 +02:00
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 ;
}
2012-08-10 19:03:22 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-08 14:36:39 +02:00
fail :
ff_rtmp_packet_destroy ( pkt );
return ret ;
}
2012-06-08 13:16:34 +02: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 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 10:51:22 +03:00
field = param + 3 ;
value = strchr ( field , ':' );
if ( ! value )
goto fail ;
* value = '\0' ;
value ++ ;
2012-06-08 13:16:34 +02:00
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 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate 'connect' call and send it to the server.
2009-07-31 06:49:36 +00:00
*/
2012-05-23 22:45:03 +02:00
static int gen_connect ( URLContext * s , RTMPContext * rt )
2009-07-31 06:49:36 +00: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 ,
2025-10-30 23:20:41 +01:00
0 , 4096 + APP_MAX_LENGTH
+ strlen ( rt -> auth_params ) + strlen ( rt -> flashver )
+ zstrlen ( rt -> enhanced_codecs ) / 5 * 7
+ zstrlen ( rt -> swfurl )
+ zstrlen ( rt -> swfverify )
+ zstrlen ( rt -> tcurl )
+ zstrlen ( rt -> auth_params )
+ zstrlen ( rt -> pageurl )
+ zstrlen ( rt -> conn ) * 3
)) < 0 )
2012-05-23 22:45:03 +02:00
return ret ;
2009-07-31 06:49:36 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "connect" );
2011-12-05 12:35:06 +02:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-07-31 06:49:36 +00:00
ff_amf_write_object_start ( & p );
ff_amf_write_field_name ( & p , "app" );
2012-12-30 22:39:38 +02:00
ff_amf_write_string2 ( & p , rt -> app , rt -> auth_params );
2009-07-31 06:49:36 +00:00
2023-08-28 09:59:24 +08:00
if ( rt -> enhanced_codecs ) {
uint32_t list_len = 0 ;
char * fourcc_data = rt -> enhanced_codecs ;
int fourcc_str_len = strlen ( fourcc_data );
// check the string, fourcc + ',' + ... + end fourcc correct length should be (4+1)*n+4
if (( fourcc_str_len + 1 ) % 5 != 0 ) {
av_log ( s , AV_LOG_ERROR , "Malformed rtmp_enhanched_codecs, "
2026-02-04 18:14:53 +08:00
"should be of the form hvc1[,av01][,vp09][,vvc1][,...] \n " );
2025-06-14 16:33:50 +08:00
ff_rtmp_packet_destroy ( & pkt );
2023-08-28 09:59:24 +08:00
return AVERROR ( EINVAL );
}
list_len = ( fourcc_str_len + 1 ) / 5 ;
ff_amf_write_field_name ( & p , "fourCcList" );
ff_amf_write_array_start ( & p , list_len );
while ( fourcc_data - rt -> enhanced_codecs < fourcc_str_len ) {
unsigned char fourcc [ 5 ];
2024-05-18 23:31:52 +02:00
if ( ! strncmp ( fourcc_data , "ac-3" , 4 ) ||
2023-08-28 09:59:24 +08:00
! strncmp ( fourcc_data , "av01" , 4 ) ||
2024-05-18 23:31:52 +02:00
! strncmp ( fourcc_data , "avc1" , 4 ) ||
! strncmp ( fourcc_data , "ec-3" , 4 ) ||
! strncmp ( fourcc_data , "fLaC" , 4 ) ||
! strncmp ( fourcc_data , "hvc1" , 4 ) ||
2026-02-04 18:14:53 +08:00
! strncmp ( fourcc_data , "vvc1" , 4 ) ||
2024-05-18 23:31:52 +02:00
! strncmp ( fourcc_data , ".mp3" , 4 ) ||
! strncmp ( fourcc_data , "mp4a" , 4 ) ||
! strncmp ( fourcc_data , "Opus" , 4 ) ||
2023-08-28 09:59:24 +08:00
! strncmp ( fourcc_data , "vp09" , 4 )) {
av_strlcpy ( fourcc , fourcc_data , sizeof ( fourcc ));
ff_amf_write_string ( & p , fourcc );
} else {
av_log ( s , AV_LOG_ERROR , "Unsupported codec fourcc, %.*s \n " , 4 , fourcc_data );
2025-06-14 16:33:50 +08:00
ff_rtmp_packet_destroy ( & pkt );
2023-08-28 09:59:24 +08:00
return AVERROR_PATCHWELCOME ;
}
fourcc_data += 5 ;
}
}
2012-05-09 02:12:15 +02:00
if ( ! rt -> is_input ) {
2009-12-04 16:52:16 +00:00
ff_amf_write_field_name ( & p , "type" );
ff_amf_write_string ( & p , "nonprivate" );
}
2009-07-31 06:49:36 +00: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
2017-03-20 20:22:51 +00:00
if ( rt -> swfurl || rt -> swfverify ) {
2012-05-09 02:12:16 +02:00
ff_amf_write_field_name ( & p , "swfUrl" );
2017-03-20 20:22:51 +00:00
if ( rt -> swfurl )
ff_amf_write_string ( & p , rt -> swfurl );
else
ff_amf_write_string ( & p , rt -> swfverify );
2012-05-09 02:12:16 +02:00
}
2009-07-31 06:49:36 +00:00
ff_amf_write_field_name ( & p , "tcUrl" );
2012-12-30 22:39:38 +02:00
ff_amf_write_string2 ( & p , rt -> tcurl , rt -> auth_params );
2009-12-04 16:52:16 +00:00
if ( rt -> is_input ) {
2009-12-04 16:52:42 +00: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 16:52:42 +00: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 16:52:42 +00: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 16:52:16 +00:00
}
2009-07-31 06:49:36 +00:00
ff_amf_write_object_end ( & p );
2012-06-08 13:16:34 +02:00
if ( rt -> conn ) {
2012-06-13 10:51:22 +03:00
char * param = rt -> conn ;
2012-06-08 13:16:34 +02:00
// Write arbitrary AMF data to the Connect message.
2014-08-14 16:31:25 -04:00
while ( param ) {
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 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 10:51:22 +03:00
if ( sep )
param = sep + 1 ;
else
break ;
2012-06-08 13:16:34 +02:00
}
}
2013-08-08 18:52:11 +02:00
pkt . size = p - pkt . data ;
2009-07-31 06:49:36 +00:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2009-07-31 06:49:36 +00:00
}
2016-10-12 15:33:44 +09:00
#define RTMP_CTRL_ABORT_MESSAGE (2)
2012-08-16 16:04:48 +02:00
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 ;
2016-10-12 15:33:44 +09:00
// handle RTMP Protocol Control Messages
for (;;) {
2014-04-13 01:06:22 +02:00
if (( ret = ff_rtmp_packet_read ( rt -> stream , & pkt , rt -> in_chunk_size ,
& rt -> prev_pkt [ 0 ], & rt -> nb_prev_pkt [ 0 ])) < 0 )
return ret ;
2016-10-12 15:33:44 +09:00
#ifdef DEBUG
ff_rtmp_packet_dump ( s , & pkt );
#endif
if ( pkt . type == RTMP_PT_CHUNK_SIZE ) {
if (( ret = handle_chunk_size ( s , & pkt )) < 0 ) {
ff_rtmp_packet_destroy ( & pkt );
return ret ;
}
} else if ( pkt . type == RTMP_CTRL_ABORT_MESSAGE ) {
av_log ( s , AV_LOG_ERROR , "received abort message \n " );
ff_rtmp_packet_destroy ( & pkt );
return AVERROR_UNKNOWN ;
} else if ( pkt . type == RTMP_PT_BYTES_READ ) {
av_log ( s , AV_LOG_TRACE , "received acknowledgement \n " );
2017-09-26 18:08:25 -03:00
} else if ( pkt . type == RTMP_PT_WINDOW_ACK_SIZE ) {
if (( ret = handle_window_ack_size ( s , & pkt )) < 0 ) {
2016-10-12 15:33:44 +09:00
ff_rtmp_packet_destroy ( & pkt );
return ret ;
}
2017-09-26 18:08:25 -03:00
} else if ( pkt . type == RTMP_PT_SET_PEER_BW ) {
if (( ret = handle_set_peer_bw ( s , & pkt )) < 0 ) {
2016-10-12 15:33:44 +09:00
ff_rtmp_packet_destroy ( & pkt );
return ret ;
}
} else if ( pkt . type == RTMP_PT_INVOKE ) {
// received RTMP Command Message
break ;
} else {
av_log ( s , AV_LOG_ERROR , "Unknown control message type (%d) \n " , pkt . type );
}
ff_rtmp_packet_destroy ( & pkt );
2014-04-13 01:06:22 +02:00
}
2012-08-16 16:04:48 +02:00
cp = pkt . data ;
2013-08-08 18:52:11 +02:00
bytestream2_init ( & gbc , cp , pkt . size );
2012-08-16 16:04:48 +02:00
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 );
2016-04-27 13:45:23 -04:00
// Send Window Acknowledgement Size (as defined in specification)
2012-08-16 16:04:48 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
2017-01-31 15:47:00 +02:00
RTMP_PT_WINDOW_ACK_SIZE , 0 , 4 )) < 0 )
2012-08-16 16:04:48 +02:00
return ret ;
p = pkt . data ;
2017-01-31 16:15:56 +02:00
// Inform the peer about how often we want acknowledgements about what
// we send. (We don't check for the acknowledgements currently.)
bytestream_put_be32 ( & p , rt -> max_sent_unacked );
2013-08-08 18:52:11 +02:00
pkt . size = p - pkt . data ;
2012-08-16 16:04:48 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , & pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
ff_rtmp_packet_destroy ( & pkt );
if ( ret < 0 )
return ret ;
2017-01-31 15:47:00 +02:00
// Set Peer Bandwidth
2012-08-16 16:04:48 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
2017-01-31 15:47:00 +02:00
RTMP_PT_SET_PEER_BW , 0 , 5 )) < 0 )
2012-08-16 16:04:48 +02:00
return ret ;
p = pkt . data ;
2017-01-31 16:15:56 +02:00
// Tell the peer to only send this many bytes unless it gets acknowledgements.
// This could be any arbitrary value we want here.
bytestream_put_be32 ( & p , rt -> max_sent_unacked );
2012-08-16 16:04:48 +02:00
bytestream_put_byte ( & p , 2 ); // dynamic
2013-08-08 18:52:11 +02:00
pkt . size = p - pkt . data ;
2012-08-16 16:04:48 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , & pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
ff_rtmp_packet_destroy ( & pkt );
if ( ret < 0 )
return ret ;
2017-01-31 15:47:00 +02:00
// User control
2012-08-16 16:04:48 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
2017-01-31 15:47:00 +02:00
RTMP_PT_USER_CONTROL , 0 , 6 )) < 0 )
2012-08-16 16:04:48 +02:00
return ret ;
p = pkt . data ;
bytestream_put_be16 ( & p , 0 ); // 0 -> Stream Begin
2017-01-31 15:47:00 +02:00
bytestream_put_be32 ( & p , 0 ); // Stream 0
2012-08-16 16:04:48 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , & pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
ff_rtmp_packet_destroy ( & pkt );
if ( ret < 0 )
return ret ;
// Chunk size
2016-10-13 16:17:11 +03:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL ,
2012-08-16 16:04:48 +02:00
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 ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
ff_rtmp_packet_destroy ( & pkt );
if ( ret < 0 )
return ret ;
2014-10-15 13:41:33 +03:00
// Send _result NetConnection.Connect.Success to connect
2012-08-16 16:04:48 +02:00
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 );
2013-08-08 18:52:11 +02:00
pkt . size = p - pkt . data ;
2012-08-16 16:04:48 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , & pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
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 );
2013-08-08 18:52:11 +02:00
pkt . size = p - pkt . data ;
2012-08-16 16:04:48 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , & pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
ff_rtmp_packet_destroy ( & pkt );
return ret ;
}
2009-12-04 16:52:16 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate 'releaseStream' call and send it to the server. It should make
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , "Releasing stream... \n " );
2009-12-04 16:52:16 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "releaseStream" );
2011-11-12 13:28:58 -08:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-12-04 16:52:16 +00:00
ff_amf_write_null ( & p );
ff_amf_write_string ( & p , rt -> playpath );
2012-08-19 13:02:23 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2009-12-04 16:52:16 +00:00
}
/**
2010-06-30 15:38:06 +00:00
* Generate 'FCPublish' call and send it to the server. It should make
2016-04-27 13:45:23 -04:00
* the server prepare for receiving media streams.
2009-12-04 16:52:16 +00:00
*/
2012-05-23 22:45:03 +02:00
static int gen_fcpublish_stream ( URLContext * s , RTMPContext * rt )
2009-12-04 16:52:16 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , "FCPublish stream... \n " );
2009-12-04 16:52:16 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "FCPublish" );
2011-11-12 13:28:58 -08:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-12-04 16:52:16 +00:00
ff_amf_write_null ( & p );
ff_amf_write_string ( & p , rt -> playpath );
2012-08-19 13:02:23 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2009-12-04 16:52:16 +00:00
}
/**
2010-06-30 15:38:06 +00:00
* Generate 'FCUnpublish' call and send it to the server. It should make
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , "UnPublishing stream... \n " );
2009-12-04 16:52:16 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "FCUnpublish" );
2011-11-12 13:28:58 -08:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-12-04 16:52:16 +00:00
ff_amf_write_null ( & p );
ff_amf_write_string ( & p , rt -> playpath );
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 0 );
2009-12-04 16:52:16 +00:00
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate 'createStream' call and send it to the server. It should make
2009-07-31 06:49:36 +00: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 06:49:36 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-07-31 06:49:36 +00: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 06:49:36 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "createStream" );
2011-11-12 13:28:58 -08:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-07-31 06:49:36 +00:00
ff_amf_write_null ( & p );
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2009-07-31 06:49:36 +00:00
}
2009-12-04 16:52:16 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate 'deleteStream' call and send it to the server. It should make
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 16:52:16 +00: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 16:52:16 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "deleteStream" );
2011-12-05 12:35:06 +02:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-12-04 16:52:16 +00:00
ff_amf_write_null ( & p );
2013-09-16 13:20:58 -07:00
ff_amf_write_number ( & p , rt -> stream_id );
2009-12-04 16:52:16 +00:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 0 );
2009-12-04 16:52:16 +00:00
}
2014-10-14 17:16:21 +02:00
/**
* Generate 'getStreamLength' call and send it to the server. If the server
* knows the duration of the selected stream, it will reply with the duration
* in seconds.
*/
static int gen_get_stream_length ( URLContext * s , RTMPContext * rt )
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_SOURCE_CHANNEL , RTMP_PT_INVOKE ,
0 , 31 + strlen ( rt -> playpath ))) < 0 )
return ret ;
p = pkt . data ;
ff_amf_write_string ( & p , "getStreamLength" );
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
ff_amf_write_null ( & p );
ff_amf_write_string ( & p , rt -> playpath );
return rtmp_send_packet ( rt , & pkt , 1 );
}
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 ;
2017-01-31 15:47:00 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_USER_CONTROL ,
2012-06-13 14:48:02 +02:00
1 , 10 )) < 0 )
return ret ;
p = pkt . data ;
2017-01-31 15:47:00 +02:00
bytestream_put_be16 ( & p , 3 ); // SetBuffer Length
2013-09-16 13:20:58 -07:00
bytestream_put_be32 ( & p , rt -> stream_id );
2012-06-13 14:48:02 +02:00
bytestream_put_be32 ( & p , rt -> client_buffer_time );
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 0 );
2012-06-13 14:48:02 +02:00
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate 'play' call and send it to the server, then ping the server
2009-07-31 06:49:36 +00:00
* to start actual playing.
*/
2012-05-23 22:45:03 +02:00
static int gen_play ( URLContext * s , RTMPContext * rt )
2009-07-31 06:49:36 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-07-31 06:49:36 +00: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
2013-09-16 13:20:57 -07:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_SOURCE_CHANNEL , RTMP_PT_INVOKE ,
2012-05-23 22:45:03 +02:00
0 , 29 + strlen ( rt -> playpath ))) < 0 )
return ret ;
2013-09-16 13:20:58 -07:00
pkt . extra = rt -> stream_id ;
2009-07-31 06:49:36 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "play" );
2011-12-05 12:35:06 +02:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-07-31 06:49:36 +00:00
ff_amf_write_null ( & p );
ff_amf_write_string ( & p , rt -> playpath );
2013-10-03 09:09:56 +02:00
ff_amf_write_number ( & p , rt -> live * 1000 );
2009-07-31 06:49:36 +00:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2009-07-31 06:49:36 +00:00
}
2013-08-02 12:29:23 +03:00
static int gen_seek ( URLContext * s , RTMPContext * rt , int64_t timestamp )
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
2013-08-08 17:43:15 +02:00
av_log ( s , AV_LOG_DEBUG , "Sending seek command for timestamp %" PRId64 " \n " ,
timestamp );
2013-08-02 12:29:23 +03:00
if (( ret = ff_rtmp_packet_create ( & pkt , 3 , RTMP_PT_INVOKE , 0 , 26 )) < 0 )
return ret ;
2013-09-16 13:20:58 -07:00
pkt . extra = rt -> stream_id ;
2013-08-02 12:29:23 +03:00
p = pkt . data ;
ff_amf_write_string ( & p , "seek" );
ff_amf_write_number ( & p , 0 ); //no tracking back responses
ff_amf_write_null ( & p ); //as usual, the first null param
ff_amf_write_number ( & p , timestamp ); //where we want to jump
return rtmp_send_packet ( rt , & pkt , 1 );
}
2014-10-17 16:30:47 +02:00
/**
* Generate a pause packet that either pauses or unpauses the current stream.
*/
static int gen_pause ( URLContext * s , RTMPContext * rt , int pause , uint32_t timestamp )
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
av_log ( s , AV_LOG_DEBUG , "Sending pause command for timestamp %d \n " ,
timestamp );
if (( ret = ff_rtmp_packet_create ( & pkt , 3 , RTMP_PT_INVOKE , 0 , 29 )) < 0 )
return ret ;
pkt . extra = rt -> stream_id ;
p = pkt . data ;
ff_amf_write_string ( & p , "pause" );
ff_amf_write_number ( & p , 0 ); //no tracking back responses
ff_amf_write_null ( & p ); //as usual, the first null param
ff_amf_write_bool ( & p , pause ); // pause or unpause
ff_amf_write_number ( & p , timestamp ); //where we pause the stream
return rtmp_send_packet ( rt , & pkt , 1 );
}
2009-12-04 16:52:16 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate 'publish' call and send it to the server.
2009-12-04 16:52:16 +00:00
*/
2012-05-23 22:45:03 +02:00
static int gen_publish ( URLContext * s , RTMPContext * rt )
2009-12-04 16:52:16 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2009-12-04 16:52:16 +00: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 ;
2013-09-16 13:20:58 -07:00
pkt . extra = rt -> stream_id ;
2009-12-04 16:52:16 +00:00
p = pkt . data ;
ff_amf_write_string ( & p , "publish" );
2011-12-05 12:35:06 +02:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2009-12-04 16:52:16 +00:00
ff_amf_write_null ( & p );
ff_amf_write_string ( & p , rt -> playpath );
ff_amf_write_string ( & p , "live" );
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2009-12-04 16:52:16 +00:00
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate ping reply and send it to the server.
2009-07-31 06:49:36 +00:00
*/
2012-05-23 22:45:03 +02:00
static int gen_pong ( URLContext * s , RTMPContext * rt , RTMPPacket * ppkt )
2009-07-31 06:49:36 +00:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2013-08-08 18:52:11 +02:00
if ( ppkt -> size < 6 ) {
2012-07-26 20:45:42 +02:00
av_log ( s , AV_LOG_ERROR , "Too short ping packet (%d) \n " ,
2013-08-08 18:52:11 +02:00
ppkt -> size );
2012-07-26 20:45:42 +02:00
return AVERROR_INVALIDDATA ;
}
2017-01-31 15:47:00 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_USER_CONTROL ,
2012-05-23 22:45:03 +02:00
ppkt -> timestamp + 1 , 6 )) < 0 )
return ret ;
2009-07-31 06:49:36 +00:00
p = pkt . data ;
2017-01-31 15:47:00 +02:00
bytestream_put_be16 ( & p , 7 ); // PingResponse
2009-12-16 12:49:38 +00:00
bytestream_put_be32 ( & p , AV_RB32 ( ppkt -> data + 2 ));
2012-05-23 22:45:03 +02:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 0 );
2009-07-31 06:49:36 +00:00
}
2012-08-13 17:05:00 +02:00
/**
* Generate SWF verification message and send it to the server.
*/
static int gen_swf_verification ( URLContext * s , RTMPContext * rt )
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
av_log ( s , AV_LOG_DEBUG , "Sending SWF verification... \n " );
2017-01-31 15:47:00 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_USER_CONTROL ,
2012-08-13 17:05:00 +02:00
0 , 44 )) < 0 )
return ret ;
p = pkt . data ;
bytestream_put_be16 ( & p , 27 );
memcpy ( p , rt -> swfverification , 42 );
return rtmp_send_packet ( rt , & pkt , 0 );
}
2012-03-07 10:21:16 -08:00
/**
2017-01-31 15:47:00 +02:00
* Generate window acknowledgement size message and send it to the server.
2012-03-07 10:21:16 -08:00
*/
2017-01-31 15:47:00 +02:00
static int gen_window_ack_size ( URLContext * s , RTMPContext * rt )
2012-03-07 10:21:16 -08:00
{
RTMPPacket pkt ;
uint8_t * p ;
2012-05-23 22:45:03 +02:00
int ret ;
2017-01-31 15:47:00 +02:00
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_NETWORK_CHANNEL , RTMP_PT_WINDOW_ACK_SIZE ,
2012-05-23 22:45:03 +02:00
0 , 4 )) < 0 )
return ret ;
2012-03-07 10:21:16 -08:00
p = pkt . data ;
2017-01-31 16:15:56 +02:00
bytestream_put_be32 ( & p , rt -> max_sent_unacked );
2012-05-23 22:45:03 +02:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 0 );
2012-03-07 10:21:16 -08: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" );
2012-08-11 12:41:33 +02:00
ff_amf_write_number ( & p , ++ rt -> nb_invokes );
2012-05-09 00:58:09 +02:00
ff_amf_write_null ( & p );
2012-08-11 12:42:17 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2012-05-09 00:58:09 +02:00
}
2010-02-18 16:27:18 +00:00
/**
2010-06-30 15:38:06 +00:00
* Generate report on bytes read so far and send it to the server.
2010-02-18 16:27:18 +00:00
*/
2012-05-23 22:45:03 +02:00
static int gen_bytes_read ( URLContext * s , RTMPContext * rt , uint32_t ts )
2010-02-18 16:27:18 +00: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 16:27:18 +00:00
p = pkt . data ;
bytestream_put_be32 ( & p , rt -> bytes_read );
2012-05-23 22:45:03 +02:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 0 );
2010-02-18 16:27:18 +00:00
}
2012-08-07 22:02:27 +02:00
static int gen_fcsubscribe_stream ( URLContext * s , RTMPContext * rt ,
const char * subscribe )
2012-08-07 21:51:46 +02:00
{
RTMPPacket pkt ;
uint8_t * p ;
int ret ;
if (( ret = ff_rtmp_packet_create ( & pkt , RTMP_SYSTEM_CHANNEL , RTMP_PT_INVOKE ,
2012-08-07 22:02:27 +02:00
0 , 27 + strlen ( subscribe ))) < 0 )
2012-08-07 21:51:46 +02: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 22:02:27 +02:00
ff_amf_write_string ( & p , subscribe );
2012-08-07 21:51:46 +02:00
2012-08-08 14:36:39 +02:00
return rtmp_send_packet ( rt , & pkt , 1 );
2012-08-07 21:51:46 +02:00
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Put HMAC-SHA2 digest of packet data (except for the bytes where this digest
2009-07-31 06:49:36 +00: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 06:49:36 +00: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 06:49:36 +00:00
{
2012-07-05 13:06:07 +02:00
int ret , digest_pos ;
2009-07-31 06:49:36 +00: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 06:49:36 +00: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 06:49:36 +00:00
return digest_pos ;
}
/**
2010-06-30 15:38:06 +00:00
* Verify that the received server response has the expected digest value.
2009-07-31 06:49:36 +00: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 06:49:36 +00:00
2012-07-05 13:06:07 +02:00
digest_pos = ff_rtmp_calc_digest_pos ( buf , off , 728 , off + 4 );
2009-07-31 06:49:36 +00: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 06:49:36 +00:00
if ( ! memcmp ( digest , buf + digest_pos , 32 ))
return digest_pos ;
return 0 ;
}
2012-08-13 17:05:00 +02:00
static int rtmp_calc_swf_verification ( URLContext * s , RTMPContext * rt ,
uint8_t * buf )
{
uint8_t * p ;
int ret ;
if ( rt -> swfhash_len != 32 ) {
av_log ( s , AV_LOG_ERROR ,
"Hash of the decompressed SWF file is not 32 bytes long. \n " );
return AVERROR ( EINVAL );
}
p = & rt -> swfverification [ 0 ];
bytestream_put_byte ( & p , 1 );
bytestream_put_byte ( & p , 1 );
bytestream_put_be32 ( & p , rt -> swfsize );
bytestream_put_be32 ( & p , rt -> swfsize );
if (( ret = ff_rtmp_calc_digest ( rt -> swfhash , 32 , 0 , buf , 32 , p )) < 0 )
return ret ;
return 0 ;
}
2012-08-15 16:11:50 +02:00
#if CONFIG_ZLIB
static int rtmp_uncompress_swfplayer ( uint8_t * in_data , int64_t in_size ,
uint8_t ** out_data , int64_t * out_size )
{
z_stream zs = { 0 };
void * ptr ;
int size ;
int ret = 0 ;
zs . avail_in = in_size ;
zs . next_in = in_data ;
ret = inflateInit ( & zs );
if ( ret != Z_OK )
return AVERROR_UNKNOWN ;
do {
uint8_t tmp_buf [ 16384 ];
zs . avail_out = sizeof ( tmp_buf );
zs . next_out = tmp_buf ;
ret = inflate ( & zs , Z_NO_FLUSH );
if ( ret != Z_OK && ret != Z_STREAM_END ) {
ret = AVERROR_UNKNOWN ;
goto fail ;
}
size = sizeof ( tmp_buf ) - zs . avail_out ;
if ( ! ( ptr = av_realloc ( * out_data , * out_size + size ))) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
* out_data = ptr ;
memcpy ( * out_data + * out_size , tmp_buf , size );
* out_size += size ;
} while ( zs . avail_out == 0 );
fail :
inflateEnd ( & zs );
return ret ;
}
#endif
static int rtmp_calc_swfhash ( URLContext * s )
{
RTMPContext * rt = s -> priv_data ;
uint8_t * in_data = NULL , * out_data = NULL , * swfdata ;
2015-10-29 13:39:49 +01:00
int64_t in_size ;
2019-12-03 17:50:45 +08:00
URLContext * stream = NULL ;
2012-08-15 16:11:50 +02:00
char swfhash [ 32 ];
int swfsize ;
int ret = 0 ;
/* Get the SWF player file. */
2016-01-30 02:17:51 +01:00
if (( ret = ffurl_open_whitelist ( & stream , rt -> swfverify , AVIO_FLAG_READ ,
& s -> interrupt_callback , NULL ,
2016-04-21 15:55:09 +01:00
s -> protocol_whitelist , s -> protocol_blacklist , s )) < 0 ) {
2012-08-15 16:11:50 +02:00
av_log ( s , AV_LOG_ERROR , "Cannot open connection %s. \n " , rt -> swfverify );
goto fail ;
}
if (( in_size = ffurl_seek ( stream , 0 , AVSEEK_SIZE )) < 0 ) {
ret = AVERROR ( EIO );
goto fail ;
}
if ( ! ( in_data = av_malloc ( in_size ))) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
if (( ret = ffurl_read_complete ( stream , in_data , in_size )) < 0 )
goto fail ;
if ( in_size < 3 ) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
if ( ! memcmp ( in_data , "CWS" , 3 )) {
2015-10-29 13:39:49 +01:00
#if CONFIG_ZLIB
int64_t out_size ;
2026-04-23 02:47:11 +00:00
if ( in_size < 8 ) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
2012-08-15 16:11:50 +02:00
/* Decompress the SWF player file using Zlib. */
if ( ! ( out_data = av_malloc ( 8 ))) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
* in_data = 'F' ; // magic stuff
memcpy ( out_data , in_data , 8 );
out_size = 8 ;
if (( ret = rtmp_uncompress_swfplayer ( in_data + 8 , in_size - 8 ,
& out_data , & out_size )) < 0 )
goto fail ;
2015-10-29 13:39:49 +01:00
swfsize = out_size ;
swfdata = out_data ;
2012-08-15 16:11:50 +02:00
#else
av_log ( s , AV_LOG_ERROR ,
"Zlib is required for decompressing the SWF player file. \n " );
ret = AVERROR ( EINVAL );
goto fail ;
#endif
} else {
swfsize = in_size ;
swfdata = in_data ;
}
/* Compute the SHA256 hash of the SWF player file. */
if (( ret = ff_rtmp_calc_digest ( swfdata , swfsize , 0 ,
"Genuine Adobe Flash Player 001" , 30 ,
swfhash )) < 0 )
goto fail ;
/* Set SWFVerification parameters. */
av_opt_set_bin ( rt , "rtmp_swfhash" , swfhash , 32 , 0 );
rt -> swfsize = swfsize ;
fail :
av_freep ( & in_data );
av_freep ( & out_data );
ffurl_close ( stream );
return ret ;
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Perform handshake with the server by means of exchanging pseudorandom data
2009-07-31 06:49:36 +00: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
2016-10-30 14:52:45 +01:00
RTMP_CLIENT_VER1 ,
RTMP_CLIENT_VER2 ,
RTMP_CLIENT_VER3 ,
RTMP_CLIENT_VER4 ,
2009-07-31 06:49:36 +00:00
};
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 ];
2025-07-21 01:45:23 +02:00
int ret ;
#if CONFIG_FFRTMPCRYPT_PROTOCOL
int type = 0 ;
#endif
2009-07-31 06:49:36 +00:00
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , "Handshaking... \n " );
2009-07-31 06:49:36 +00: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
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
if ( rt -> encrypted ) {
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 ;
}
2025-07-21 01:45:23 +02:00
#endif
2012-07-19 14:13:58 +02:00
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 06:49:36 +00: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 ;
2009-07-31 06:49:36 +00:00
}
2012-05-24 13:48:25 +02:00
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 06:49:36 +00: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 06:49:36 +00: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 06:49:36 +00:00
serverdata [ 5 ], serverdata [ 6 ], serverdata [ 7 ], serverdata [ 8 ]);
2010-01-12 06:44:49 +00:00
if ( rt -> is_input && serverdata [ 5 ] >= 3 ) {
2009-12-04 16:52:42 +00: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 06:49:36 +00:00
if ( ! server_pos ) {
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
2012-07-19 14:13:58 +02:00
type = 1 ;
2025-07-21 01:45:23 +02:00
#endif
2009-12-04 16:52:42 +00: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 16:52:42 +00: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 16:52:42 +00:00
}
}
2012-08-13 17:05:00 +02:00
/* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF,
* key are the last 32 bytes of the server handshake. */
if ( rt -> swfsize ) {
if (( ret = rtmp_calc_swf_verification ( s , rt , serverdata + 1 +
RTMP_HANDSHAKE_PACKET_SIZE - 32 )) < 0 )
return ret ;
}
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 ;
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
if ( rt -> encrypted ) {
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 ]);
}
2025-07-21 01:45:23 +02:00
#endif
2012-07-19 14:13:58 +02:00
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-07-31 06:49:36 +00:00
}
2009-12-04 16:52:42 +00: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-07-31 06:49:36 +00:00
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
if ( rt -> encrypted ) {
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 ]);
}
2025-07-21 01:45:23 +02:00
#endif
2012-07-19 14:13:58 +02:00
2009-12-04 16:52:42 +00: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
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
if ( rt -> encrypted ) {
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 ;
}
2025-07-21 01:45:23 +02:00
#endif
2009-12-04 16:52:16 +00:00
} else {
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
if ( rt -> encrypted ) {
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 ]);
}
}
2025-07-21 01:45:23 +02:00
#endif
2012-07-19 14:13:58 +02:00
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
2025-07-21 01:45:23 +02:00
#if CONFIG_FFRTMPCRYPT_PROTOCOL
if ( rt -> encrypted ) {
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 ;
}
2025-07-21 01:45:23 +02:00
#endif
2009-12-04 16:52:16 +00:00
}
2009-07-31 06:49:36 +00:00
return 0 ;
}
2012-08-16 16:04:48 +02:00
static int rtmp_receive_hs_packet ( RTMPContext * rt , uint32_t * first_int ,
uint32_t * second_int , char * arraydata ,
int size )
{
2012-08-16 22:11:13 +03:00
int inoutsize ;
2012-08-16 16:04:48 +02:00
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 )
{
2012-08-16 22:11:13 +03:00
int inoutsize ;
2012-08-16 16:04:48 +02:00
AV_WB32 ( arraydata , first_int );
2013-09-16 11:39:45 +02:00
AV_WB32 ( arraydata + 4 , second_int );
2012-08-16 16:04:48 +02:00
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 ;
2012-08-16 22:11:13 +03:00
int inoutsize = 0 ;
2012-08-16 16:04:48 +02:00
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 ;
}
/* Send S1 */
/* By now same epoch will be sent */
hs_my_epoch = hs_epoch ;
/* Generate random */
2012-10-12 02:06:51 +02:00
for ( randomidx = 8 ; randomidx < ( RTMP_HANDSHAKE_PACKET_SIZE );
2012-08-16 16:04:48 +02:00
randomidx += 4 )
2012-10-12 02:06:51 +02:00
AV_WB32 ( hs_s1 + randomidx , av_get_random_seed ());
2012-08-16 16:04:48 +02:00
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 ;
}
2012-07-21 12:59:52 +02:00
static int handle_chunk_size ( URLContext * s , RTMPPacket * pkt )
2009-07-31 06:49:36 +00:00
{
2012-07-21 12:59:52 +02:00
RTMPContext * rt = s -> priv_data ;
int ret ;
2013-08-08 18:52:11 +02:00
if ( pkt -> 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 " ,
2013-08-08 18:52:11 +02:00
pkt -> size );
2012-07-25 20:51:08 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:52 +02:00
}
if ( ! rt -> is_input ) {
2012-08-10 19:03:22 +02:00
/* Send the same chunk size change packet back to the server,
* setting the outgoing chunk size to the same as the incoming one. */
if (( ret = ff_rtmp_packet_write ( rt -> stream , pkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ])) < 0 )
2012-07-21 12:59:52 +02:00
return ret ;
2012-08-10 19:03:22 +02:00
rt -> out_chunk_size = AV_RB32 ( pkt -> data );
2012-07-21 12:59:52 +02:00
}
2012-08-10 19:03:22 +02:00
rt -> in_chunk_size = AV_RB32 ( pkt -> data );
if ( rt -> in_chunk_size <= 0 ) {
av_log ( s , AV_LOG_ERROR , "Incorrect chunk size %d \n " ,
rt -> in_chunk_size );
2012-07-25 20:51:08 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:52 +02:00
}
2012-08-10 19:03:22 +02:00
av_log ( s , AV_LOG_DEBUG , "New incoming chunk size = %d \n " ,
rt -> in_chunk_size );
2012-07-21 12:59:52 +02:00
return 0 ;
}
2017-01-31 15:47:00 +02:00
static int handle_user_control ( URLContext * s , RTMPPacket * pkt )
2012-07-21 12:59:51 +02:00
{
RTMPContext * rt = s -> priv_data ;
int t , ret ;
2013-08-08 18:52:11 +02:00
if ( pkt -> size < 2 ) {
2017-01-31 15:47:00 +02:00
av_log ( s , AV_LOG_ERROR , "Too short user control packet (%d) \n " ,
2013-08-08 18:52:11 +02:00
pkt -> size );
2012-07-26 20:45:42 +02:00
return AVERROR_INVALIDDATA ;
}
2012-07-21 12:59:51 +02:00
t = AV_RB16 ( pkt -> data );
2017-01-31 15:47:00 +02:00
if ( t == 6 ) { // PingRequest
2012-07-21 12:59:51 +02:00
if (( ret = gen_pong ( s , rt , pkt )) < 0 )
return ret ;
2012-08-13 17:05:00 +02:00
} else if ( t == 26 ) {
if ( rt -> swfsize ) {
if (( ret = gen_swf_verification ( s , rt )) < 0 )
return ret ;
} else {
av_log ( s , AV_LOG_WARNING , "Ignoring SWFVerification request. \n " );
}
2012-07-21 12:59:51 +02:00
}
return 0 ;
}
2017-01-31 15:47:00 +02:00
static int handle_set_peer_bw ( URLContext * s , RTMPPacket * pkt )
2012-07-21 12:59:50 +02:00
{
RTMPContext * rt = s -> priv_data ;
2013-08-08 18:52:11 +02:00
if ( pkt -> size < 4 ) {
2012-07-21 12:59:50 +02:00
av_log ( s , AV_LOG_ERROR ,
2017-01-31 15:47:00 +02:00
"Peer bandwidth packet is less than 4 bytes long (%d) \n " ,
2013-08-08 18:52:11 +02:00
pkt -> 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
2017-01-31 16:15:56 +02:00
// We currently don't check how much the peer has acknowledged of
// what we have sent. To do that properly, we should call
// gen_window_ack_size here, to tell the peer that we want an
// acknowledgement with (at least) that interval.
rt -> max_sent_unacked = AV_RB32 ( pkt -> data );
if ( rt -> max_sent_unacked <= 0 ) {
av_log ( s , AV_LOG_ERROR , "Incorrect set peer bandwidth %d \n " ,
rt -> max_sent_unacked );
2012-07-25 20:51:11 +02:00
return AVERROR_INVALIDDATA ;
}
2017-01-31 16:15:56 +02:00
av_log ( s , AV_LOG_DEBUG , "Max sent, unacked = %d \n " , rt -> max_sent_unacked );
2012-07-21 12:59:50 +02:00
return 0 ;
}
2017-01-31 15:47:00 +02:00
static int handle_window_ack_size ( URLContext * s , RTMPPacket * pkt )
2012-07-21 12:59:49 +02:00
{
RTMPContext * rt = s -> priv_data ;
2013-08-08 18:52:11 +02:00
if ( pkt -> size < 4 ) {
2012-07-26 14:05:18 +02:00
av_log ( s , AV_LOG_ERROR ,
2017-01-31 15:47:00 +02:00
"Too short window acknowledgement size packet (%d) \n " ,
2013-08-08 18:52:11 +02:00
pkt -> size );
2012-07-26 14:05:18 +02:00
return AVERROR_INVALIDDATA ;
}
2017-01-31 16:15:56 +02:00
rt -> receive_report_size = AV_RB32 ( pkt -> data );
if ( rt -> receive_report_size <= 0 ) {
2017-01-31 15:47:00 +02:00
av_log ( s , AV_LOG_ERROR , "Incorrect window acknowledgement size %d \n " ,
2017-01-31 16:15:56 +02:00
rt -> receive_report_size );
2012-07-25 20:51:10 +02:00
return AVERROR_INVALIDDATA ;
2012-07-21 12:59:49 +02:00
}
2017-01-31 16:15:56 +02:00
av_log ( s , AV_LOG_DEBUG , "Window acknowledgement size = %d \n " , rt -> receive_report_size );
// Send an Acknowledgement packet after receiving half the maximum
// size, to make sure the peer can keep on sending without waiting
// for acknowledgements.
rt -> receive_report_size >>= 1 ;
2012-07-21 12:59:49 +02:00
return 0 ;
}
2012-12-30 22:39:38 +02:00
static int do_adobe_auth ( RTMPContext * rt , const char * user , const char * salt ,
const char * opaque , const char * challenge )
{
uint8_t hash [ 16 ];
char hashstr [ AV_BASE64_SIZE ( sizeof ( hash ))], challenge2 [ 10 ];
struct AVMD5 * md5 = av_md5_alloc ();
if ( ! md5 )
return AVERROR ( ENOMEM );
snprintf ( challenge2 , sizeof ( challenge2 ), "%08x" , av_get_random_seed ());
av_md5_init ( md5 );
av_md5_update ( md5 , user , strlen ( user ));
av_md5_update ( md5 , salt , strlen ( salt ));
av_md5_update ( md5 , rt -> password , strlen ( rt -> password ));
av_md5_final ( md5 , hash );
av_base64_encode ( hashstr , sizeof ( hashstr ), hash ,
sizeof ( hash ));
av_md5_init ( md5 );
av_md5_update ( md5 , hashstr , strlen ( hashstr ));
if ( opaque )
av_md5_update ( md5 , opaque , strlen ( opaque ));
else if ( challenge )
av_md5_update ( md5 , challenge , strlen ( challenge ));
av_md5_update ( md5 , challenge2 , strlen ( challenge2 ));
av_md5_final ( md5 , hash );
av_base64_encode ( hashstr , sizeof ( hashstr ), hash ,
sizeof ( hash ));
snprintf ( rt -> auth_params , sizeof ( rt -> auth_params ),
"?authmod=%s&user=%s&challenge=%s&response=%s" ,
"adobe" , user , challenge2 , hashstr );
if ( opaque )
av_strlcatf ( rt -> auth_params , sizeof ( rt -> auth_params ),
"&opaque=%s" , opaque );
av_free ( md5 );
return 0 ;
}
2012-12-31 00:46:14 +02:00
static int do_llnw_auth ( RTMPContext * rt , const char * user , const char * nonce )
{
uint8_t hash [ 16 ];
char hashstr1 [ 33 ], hashstr2 [ 33 ];
const char * realm = "live" ;
const char * method = "publish" ;
const char * qop = "auth" ;
const char * nc = "00000001" ;
char cnonce [ 10 ];
struct AVMD5 * md5 = av_md5_alloc ();
if ( ! md5 )
return AVERROR ( ENOMEM );
snprintf ( cnonce , sizeof ( cnonce ), "%08x" , av_get_random_seed ());
av_md5_init ( md5 );
av_md5_update ( md5 , user , strlen ( user ));
av_md5_update ( md5 , ":" , 1 );
av_md5_update ( md5 , realm , strlen ( realm ));
av_md5_update ( md5 , ":" , 1 );
av_md5_update ( md5 , rt -> password , strlen ( rt -> password ));
av_md5_final ( md5 , hash );
ff_data_to_hex ( hashstr1 , hash , 16 , 1 );
av_md5_init ( md5 );
av_md5_update ( md5 , method , strlen ( method ));
av_md5_update ( md5 , ":/" , 2 );
av_md5_update ( md5 , rt -> app , strlen ( rt -> app ));
2013-08-15 13:17:15 +03:00
if ( ! strchr ( rt -> app , '/' ))
av_md5_update ( md5 , "/_definst_" , strlen ( "/_definst_" ));
2012-12-31 00:46:14 +02:00
av_md5_final ( md5 , hash );
ff_data_to_hex ( hashstr2 , hash , 16 , 1 );
av_md5_init ( md5 );
av_md5_update ( md5 , hashstr1 , strlen ( hashstr1 ));
av_md5_update ( md5 , ":" , 1 );
if ( nonce )
av_md5_update ( md5 , nonce , strlen ( nonce ));
av_md5_update ( md5 , ":" , 1 );
av_md5_update ( md5 , nc , strlen ( nc ));
av_md5_update ( md5 , ":" , 1 );
av_md5_update ( md5 , cnonce , strlen ( cnonce ));
av_md5_update ( md5 , ":" , 1 );
av_md5_update ( md5 , qop , strlen ( qop ));
av_md5_update ( md5 , ":" , 1 );
av_md5_update ( md5 , hashstr2 , strlen ( hashstr2 ));
av_md5_final ( md5 , hash );
ff_data_to_hex ( hashstr1 , hash , 16 , 1 );
snprintf ( rt -> auth_params , sizeof ( rt -> auth_params ),
"?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s" ,
"llnw" , user , nonce , cnonce , nc , hashstr1 );
av_free ( md5 );
return 0 ;
}
2012-12-30 22:39:38 +02:00
static int handle_connect_error ( URLContext * s , const char * desc )
{
RTMPContext * rt = s -> priv_data ;
2012-12-31 00:46:14 +02:00
char buf [ 300 ], * ptr , authmod [ 15 ];
2012-12-30 22:39:38 +02:00
int i = 0 , ret = 0 ;
const char * user = "" , * salt = "" , * opaque = NULL ,
2012-12-31 00:46:14 +02:00
* challenge = NULL , * cptr = NULL , * nonce = NULL ;
2012-12-30 22:39:38 +02:00
2012-12-31 00:46:14 +02:00
if ( ! ( cptr = strstr ( desc , "authmod=adobe" )) &&
! ( cptr = strstr ( desc , "authmod=llnw" ))) {
2012-12-30 22:39:38 +02:00
av_log ( s , AV_LOG_ERROR ,
"Unknown connect error (unsupported authentication method?) \n " );
return AVERROR_UNKNOWN ;
}
2012-12-31 00:46:14 +02:00
cptr += strlen ( "authmod=" );
while ( * cptr && * cptr != ' ' && i < sizeof ( authmod ) - 1 )
authmod [ i ++ ] = * cptr ++ ;
authmod [ i ] = '\0' ;
2012-12-30 22:39:38 +02:00
if ( ! rt -> username [ 0 ] || ! rt -> password [ 0 ]) {
av_log ( s , AV_LOG_ERROR , "No credentials set \n " );
return AVERROR_UNKNOWN ;
}
if ( strstr ( desc , "?reason=authfailed" )) {
av_log ( s , AV_LOG_ERROR , "Incorrect username/password \n " );
return AVERROR_UNKNOWN ;
} else if ( strstr ( desc , "?reason=nosuchuser" )) {
av_log ( s , AV_LOG_ERROR , "Incorrect username \n " );
return AVERROR_UNKNOWN ;
}
if ( rt -> auth_tried ) {
av_log ( s , AV_LOG_ERROR , "Authentication failed \n " );
return AVERROR_UNKNOWN ;
}
rt -> auth_params [ 0 ] = '\0' ;
if ( strstr ( desc , "code=403 need auth" )) {
snprintf ( rt -> auth_params , sizeof ( rt -> auth_params ),
2012-12-31 00:46:14 +02:00
"?authmod=%s&user=%s" , authmod , rt -> username );
2012-12-30 22:39:38 +02:00
return 0 ;
}
if ( ! ( cptr = strstr ( desc , "?reason=needauth" ))) {
av_log ( s , AV_LOG_ERROR , "No auth parameters found \n " );
return AVERROR_UNKNOWN ;
}
av_strlcpy ( buf , cptr + 1 , sizeof ( buf ));
ptr = buf ;
while ( ptr ) {
char * next = strchr ( ptr , '&' );
char * value = strchr ( ptr , '=' );
if ( next )
* next ++ = '\0' ;
2014-05-16 13:22:21 -04:00
if ( value ) {
2012-12-30 22:39:38 +02:00
* value ++ = '\0' ;
2014-05-16 13:22:21 -04:00
if ( ! strcmp ( ptr , "user" )) {
user = value ;
} else if ( ! strcmp ( ptr , "salt" )) {
salt = value ;
} else if ( ! strcmp ( ptr , "opaque" )) {
opaque = value ;
} else if ( ! strcmp ( ptr , "challenge" )) {
challenge = value ;
} else if ( ! strcmp ( ptr , "nonce" )) {
nonce = value ;
2014-05-16 13:39:03 -04:00
} else {
av_log ( s , AV_LOG_INFO , "Ignoring unsupported var %s \n " , ptr );
2014-05-16 13:22:21 -04:00
}
} else {
av_log ( s , AV_LOG_WARNING , "Variable %s has NULL value \n " , ptr );
2012-12-30 22:39:38 +02:00
}
ptr = next ;
}
2012-12-31 00:46:14 +02:00
if ( ! strcmp ( authmod , "adobe" )) {
2013-03-20 11:07:53 +02:00
if (( ret = do_adobe_auth ( rt , user , salt , opaque , challenge )) < 0 )
2012-12-31 00:46:14 +02:00
return ret ;
} else {
if (( ret = do_llnw_auth ( rt , user , nonce )) < 0 )
return ret ;
}
2012-12-30 22:39:38 +02:00
rt -> auth_tried = 1 ;
return 0 ;
}
2012-08-09 14:57:51 +02:00
static int handle_invoke_error ( URLContext * s , RTMPPacket * pkt )
{
2012-12-30 22:39:38 +02:00
RTMPContext * rt = s -> priv_data ;
2013-08-08 18:52:11 +02:00
const uint8_t * data_end = pkt -> data + pkt -> size ;
2012-08-11 12:42:17 +02:00
char * tracked_method = NULL ;
int level = AV_LOG_ERROR ;
2012-08-09 14:57:51 +02:00
uint8_t tmpstr [ 256 ];
2012-08-11 12:42:17 +02:00
int ret ;
if (( ret = find_tracked_method ( s , pkt , 9 , & tracked_method )) < 0 )
return ret ;
2012-08-09 14:57:51 +02:00
if ( ! ff_amf_get_field_value ( pkt -> data + 9 , data_end ,
"description" , tmpstr , sizeof ( tmpstr ))) {
2012-08-19 13:02:23 +02:00
if ( tracked_method && ( ! strcmp ( tracked_method , "_checkbw" ) ||
! strcmp ( tracked_method , "releaseStream" ) ||
! strcmp ( tracked_method , "FCSubscribe" ) ||
! strcmp ( tracked_method , "FCPublish" ))) {
/* Gracefully ignore Adobe-specific historical artifact errors. */
2012-08-11 12:42:17 +02:00
level = AV_LOG_WARNING ;
ret = 0 ;
2014-11-04 09:23:35 +02:00
} else if ( tracked_method && ! strcmp ( tracked_method , "getStreamLength" )) {
level = rt -> live ? AV_LOG_DEBUG : AV_LOG_WARNING ;
2014-11-04 00:35:32 +01:00
ret = 0 ;
2012-12-30 22:39:38 +02:00
} else if ( tracked_method && ! strcmp ( tracked_method , "connect" )) {
ret = handle_connect_error ( s , tmpstr );
if ( ! ret ) {
rt -> do_reconnect = 1 ;
level = AV_LOG_VERBOSE ;
}
2012-08-11 12:42:17 +02:00
} else
2012-12-30 18:53:35 +02:00
ret = AVERROR_UNKNOWN ;
2012-08-11 12:42:17 +02:00
av_log ( s , level , "Server error: %s \n " , tmpstr );
2012-08-09 14:57:51 +02:00
}
2012-08-11 12:42:17 +02:00
av_free ( tracked_method );
return ret ;
2012-08-09 14:57:51 +02:00
}
2013-09-14 16:41:50 +02:00
static int write_begin ( URLContext * s )
{
RTMPContext * rt = s -> priv_data ;
PutByteContext pbc ;
RTMPPacket spkt = { 0 };
int ret ;
// Send Stream Begin 1
if (( ret = ff_rtmp_packet_create ( & spkt , RTMP_NETWORK_CHANNEL ,
2017-01-31 15:47:00 +02:00
RTMP_PT_USER_CONTROL , 0 , 6 )) < 0 ) {
2013-09-14 16:41:50 +02:00
av_log ( s , AV_LOG_ERROR , "Unable to create response packet \n " );
return ret ;
}
bytestream2_init_writer ( & pbc , spkt . data , spkt . 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 ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2013-09-14 16:41:50 +02:00
ff_rtmp_packet_destroy ( & spkt );
return ret ;
}
static int write_status ( URLContext * s , RTMPPacket * pkt ,
2022-04-14 22:40:28 +02:00
const char * status , const char * description , const char * details )
2013-09-14 16:41:50 +02:00
{
RTMPContext * rt = s -> priv_data ;
RTMPPacket spkt = { 0 };
uint8_t * pp ;
int ret ;
if (( ret = ff_rtmp_packet_create ( & spkt , RTMP_SYSTEM_CHANNEL ,
RTMP_PT_INVOKE , 0 ,
2025-10-30 23:20:41 +01:00
RTMP_PKTDATA_DEFAULT_SIZE
+ strlen ( status ) + strlen ( description )
+ zstrlen ( details ))) < 0 ) {
2013-09-14 16:41:50 +02:00
av_log ( s , AV_LOG_ERROR , "Unable to create response packet \n " );
return ret ;
}
pp = spkt . data ;
spkt . extra = pkt -> extra ;
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 , status );
ff_amf_write_field_name ( & pp , "description" );
2022-04-14 22:40:28 +02:00
ff_amf_write_string ( & pp , description );
if ( details ) {
ff_amf_write_field_name ( & pp , "details" );
ff_amf_write_string ( & pp , details );
}
2013-09-14 16:41:50 +02:00
ff_amf_write_object_end ( & pp );
spkt . size = pp - spkt . data ;
ret = ff_rtmp_packet_write ( rt -> stream , & spkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2013-09-14 16:41:50 +02:00
ff_rtmp_packet_destroy ( & spkt );
return ret ;
}
2012-08-16 16:04:48 +02:00
static int send_invoke_response ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s -> priv_data ;
double seqnum ;
2016-10-13 15:25:33 +03:00
char filename [ 128 ];
2012-08-16 16:04:48 +02:00
char command [ 64 ];
int stringlen ;
char * pchar ;
const uint8_t * p = pkt -> data ;
uint8_t * pp = NULL ;
RTMPPacket spkt = { 0 };
GetByteContext gbc ;
int ret ;
2013-08-08 18:52:11 +02:00
bytestream2_init ( & gbc , p , pkt -> size );
2012-08-16 16:04:48 +02:00
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 );
2016-10-13 15:24:54 +03:00
if ( ret ) {
if ( ret == AVERROR ( EINVAL ))
av_log ( s , AV_LOG_ERROR , "Unable to parse stream name - name too long? \n " );
else
av_log ( s , AV_LOG_ERROR , "Unable to parse stream name \n " );
return ret ;
}
2012-08-16 16:04:48 +02:00
// 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" )) {
2024-12-12 20:53:51 +01:00
char statusmsg [ sizeof ( filename ) + 32 ];
2022-04-14 22:40:28 +02:00
snprintf ( statusmsg , sizeof ( statusmsg ), "%s is now published" , filename );
2013-09-14 16:41:50 +02:00
ret = write_begin ( s );
2012-08-16 16:04:48 +02:00
if ( ret < 0 )
return ret ;
// Send onStatus(NetStream.Publish.Start)
2013-09-14 16:41:50 +02:00
return write_status ( s , pkt , "NetStream.Publish.Start" ,
2022-04-14 22:40:28 +02:00
statusmsg , filename );
2013-09-15 16:52:33 +02:00
} else if ( ! strcmp ( command , "play" )) {
ret = write_begin ( s );
if ( ret < 0 )
return ret ;
rt -> state = STATE_SENDING ;
return write_status ( s , pkt , "NetStream.Play.Start" ,
2022-04-14 22:40:28 +02:00
"playing stream" , NULL );
2012-08-16 16:04:48 +02:00
} 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. */
}
}
2013-08-08 18:52:11 +02:00
spkt . size = pp - spkt . data ;
2012-08-16 16:04:48 +02:00
ret = ff_rtmp_packet_write ( rt -> stream , & spkt , rt -> out_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 1 ], & rt -> nb_prev_pkt [ 1 ]);
2012-08-16 16:04:48 +02:00
ff_rtmp_packet_destroy ( & spkt );
return ret ;
}
2014-10-14 17:16:20 +02:00
/**
* Read the AMF_NUMBER response ("_result") to a function call
* (e.g. createStream()). This response should be made up of the AMF_STRING
* "result", a NULL object and then the response encoded as AMF_NUMBER. On a
* successful response, we will return set the value to number (otherwise number
* will not be changed).
*
2016-04-27 13:45:23 -04:00
* @return 0 if reading the value succeeds, negative value otherwise
2014-10-14 17:16:20 +02:00
*/
static int read_number_result ( RTMPPacket * pkt , double * number )
{
// We only need to fit "_result" in this.
uint8_t strbuffer [ 8 ];
int stringlen ;
double numbuffer ;
GetByteContext gbc ;
bytestream2_init ( & gbc , pkt -> data , pkt -> size );
// Value 1/4: "_result" as AMF_STRING
if ( ff_amf_read_string ( & gbc , strbuffer , sizeof ( strbuffer ), & stringlen ))
return AVERROR_INVALIDDATA ;
if ( strcmp ( strbuffer , "_result" ))
return AVERROR_INVALIDDATA ;
// Value 2/4: The callee reference number
if ( ff_amf_read_number ( & gbc , & numbuffer ))
return AVERROR_INVALIDDATA ;
// Value 3/4: Null
if ( ff_amf_read_null ( & gbc ))
return AVERROR_INVALIDDATA ;
2016-04-27 13:45:23 -04:00
// Value 4/4: The response as AMF_NUMBER
2014-10-14 17:16:20 +02:00
if ( ff_amf_read_number ( & gbc , & numbuffer ))
return AVERROR_INVALIDDATA ;
else
* number = numbuffer ;
return 0 ;
}
2012-08-09 14:57:49 +02:00
static int handle_invoke_result ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s -> priv_data ;
char * tracked_method = NULL ;
int ret = 0 ;
2012-08-11 12:41:32 +02:00
if (( ret = find_tracked_method ( s , pkt , 10 , & tracked_method )) < 0 )
2012-08-09 14:57:49 +02:00
return ret ;
if ( ! tracked_method ) {
/* Ignore this reply when the current method is not tracked. */
return ret ;
}
2013-08-08 19:44:19 +02:00
if ( ! strcmp ( tracked_method , "connect" )) {
2012-08-09 14:57:49 +02:00
if ( ! rt -> is_input ) {
if (( ret = gen_release_stream ( s , rt )) < 0 )
goto fail ;
if (( ret = gen_fcpublish_stream ( s , rt )) < 0 )
goto fail ;
} else {
2017-01-31 15:47:00 +02:00
if (( ret = gen_window_ack_size ( s , rt )) < 0 )
2012-08-09 14:57:49 +02:00
goto fail ;
}
if (( ret = gen_create_stream ( s , rt )) < 0 )
goto 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 fail ;
} else if ( rt -> live == - 1 ) {
if (( ret = gen_fcsubscribe_stream ( s , rt , rt -> playpath )) < 0 )
goto fail ;
}
}
2013-08-08 19:44:19 +02:00
} else if ( ! strcmp ( tracked_method , "createStream" )) {
2014-10-14 17:16:20 +02:00
double stream_id ;
if ( read_number_result ( pkt , & stream_id )) {
2012-08-09 14:57:49 +02:00
av_log ( s , AV_LOG_WARNING , "Unexpected reply on connect() \n " );
} else {
2014-10-14 17:16:20 +02:00
rt -> stream_id = stream_id ;
2012-08-09 14:57:49 +02:00
}
if ( ! rt -> is_input ) {
if (( ret = gen_publish ( s , rt )) < 0 )
goto fail ;
} else {
2014-10-14 17:16:21 +02:00
if ( rt -> live != - 1 ) {
if (( ret = gen_get_stream_length ( s , rt )) < 0 )
goto fail ;
}
2012-08-09 14:57:49 +02:00
if (( ret = gen_play ( s , rt )) < 0 )
goto fail ;
if (( ret = gen_buffer_time ( s , rt )) < 0 )
goto fail ;
}
2014-10-14 17:16:21 +02:00
} else if ( ! strcmp ( tracked_method , "getStreamLength" )) {
if ( read_number_result ( pkt , & rt -> duration )) {
av_log ( s , AV_LOG_WARNING , "Unexpected reply on getStreamLength() \n " );
}
2012-08-09 14:57:49 +02:00
}
fail :
av_free ( tracked_method );
return ret ;
}
2012-08-09 14:57:50 +02:00
static int handle_invoke_status ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s -> priv_data ;
2013-08-08 18:52:11 +02:00
const uint8_t * data_end = pkt -> data + pkt -> size ;
2013-09-21 21:16:04 +02:00
const uint8_t * ptr = pkt -> data + RTMP_HEADER ;
2012-08-09 14:57:50 +02:00
uint8_t tmpstr [ 256 ];
int i , t ;
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" )) {
2013-10-03 07:44:51 +02:00
t = ff_amf_get_field_value ( ptr , data_end ,
"description" , tmpstr , sizeof ( tmpstr ));
if ( t || ! tmpstr [ 0 ])
t = ff_amf_get_field_value ( ptr , data_end , "code" ,
tmpstr , sizeof ( tmpstr ));
if ( ! t )
2012-08-09 14:57:50 +02:00
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 ;
2013-08-02 12:29:23 +03:00
if ( ! t && ! strcmp ( tmpstr , "NetStream.Seek.Notify" )) rt -> state = STATE_PLAYING ;
2012-08-09 14:57:50 +02:00
return 0 ;
}
2012-07-21 12:59:58 +02:00
static int handle_invoke ( URLContext * s , RTMPPacket * pkt )
2009-07-31 06:49:36 +00:00
{
2012-07-21 12:59:58 +02:00
RTMPContext * rt = s -> priv_data ;
2012-08-08 14:36:39 +02:00
int ret = 0 ;
2009-07-31 06:49:36 +00:00
2012-07-21 12:59:58 +02:00
//TODO: check for the messages sent for wrong state?
2013-08-08 19:44:19 +02:00
if ( ff_amf_match_string ( pkt -> data , pkt -> size , "_error" )) {
2012-08-09 14:57:51 +02:00
if (( ret = handle_invoke_error ( s , pkt )) < 0 )
return ret ;
2013-08-08 19:44:19 +02:00
} else if ( ff_amf_match_string ( pkt -> data , pkt -> size , "_result" )) {
2012-08-09 14:57:49 +02:00
if (( ret = handle_invoke_result ( s , pkt )) < 0 )
2012-08-08 14:36:39 +02:00
return ret ;
2013-08-08 19:44:19 +02:00
} else if ( ff_amf_match_string ( pkt -> data , pkt -> size , "onStatus" )) {
2012-08-09 14:57:50 +02:00
if (( ret = handle_invoke_status ( s , pkt )) < 0 )
return ret ;
2013-08-08 19:44:19 +02:00
} else if ( ff_amf_match_string ( pkt -> data , pkt -> size , "onBWDone" )) {
2012-07-21 12:59:58 +02:00
if (( ret = gen_check_bw ( s , rt )) < 0 )
return ret ;
2013-08-08 19:44:19 +02:00
} else if ( ff_amf_match_string ( pkt -> data , pkt -> size , "releaseStream" ) ||
ff_amf_match_string ( pkt -> data , pkt -> size , "FCPublish" ) ||
ff_amf_match_string ( pkt -> data , pkt -> size , "publish" ) ||
2013-09-15 16:52:33 +02:00
ff_amf_match_string ( pkt -> data , pkt -> size , "play" ) ||
2013-08-08 19:44:19 +02:00
ff_amf_match_string ( pkt -> data , pkt -> size , "_checkbw" ) ||
ff_amf_match_string ( pkt -> data , pkt -> size , "createStream" )) {
2013-01-14 00:07:51 +01:00
if (( ret = send_invoke_response ( s , pkt )) < 0 )
2012-08-16 16:04:48 +02:00
return ret ;
2012-07-21 12:59:58 +02:00
}
2012-08-08 14:36:39 +02:00
return ret ;
2012-07-21 12:59:58 +02:00
}
2013-09-21 11:09:39 +02:00
static int update_offset ( RTMPContext * rt , int size )
{
int old_flv_size ;
2013-09-16 17:42:59 +02:00
2026-04-18 09:38:08 -03:00
if ( size < 0 )
return AVERROR ( EINVAL );
2013-09-16 17:42:59 +02:00
// generate packet header and put data into buffer for FLV demuxer
if ( rt -> flv_off < rt -> flv_size ) {
2013-09-19 12:48:04 +03:00
// There is old unread data in the buffer, thus append at the end
2026-04-18 09:38:08 -03:00
if ( rt -> flv_size > INT_MAX - size )
return AVERROR ( ERANGE );
2013-09-16 17:42:59 +02:00
old_flv_size = rt -> flv_size ;
2013-10-03 12:29:37 +02:00
rt -> flv_size += size ;
2013-09-16 17:42:59 +02:00
} else {
2013-09-19 12:48:04 +03:00
// All data has been read, write the new data at the start of the buffer
2013-09-16 17:42:59 +02:00
old_flv_size = 0 ;
2013-10-03 12:29:37 +02:00
rt -> flv_size = size ;
2013-09-16 17:42:59 +02:00
rt -> flv_off = 0 ;
}
2013-09-19 01:49:41 +02:00
2013-09-21 11:09:39 +02:00
return old_flv_size ;
}
static int append_flv_data ( RTMPContext * rt , RTMPPacket * pkt , int skip )
{
int old_flv_size , ret ;
PutByteContext pbc ;
const uint8_t * data = pkt -> data + skip ;
const int size = pkt -> size - skip ;
uint32_t ts = pkt -> timestamp ;
2014-05-31 20:37:26 +01:00
if ( pkt -> type == RTMP_PT_AUDIO ) {
rt -> has_audio = 1 ;
} else if ( pkt -> type == RTMP_PT_VIDEO ) {
rt -> has_video = 1 ;
}
2026-04-18 09:38:08 -03:00
if ( size > INT_MAX - 15 )
return AVERROR ( ERANGE );
2013-10-03 12:29:37 +02:00
old_flv_size = update_offset ( rt , size + 15 );
2026-04-18 09:38:08 -03:00
if ( old_flv_size < 0 )
return old_flv_size ;
2013-09-21 11:09:39 +02:00
2013-10-03 12:27:10 +02:00
if (( ret = av_reallocp ( & rt -> flv_data , rt -> flv_size )) < 0 ) {
rt -> flv_size = rt -> flv_off = 0 ;
2013-09-21 11:09:39 +02:00
return ret ;
2013-10-03 12:27:10 +02:00
}
2013-09-19 01:49:41 +02:00
bytestream2_init_writer ( & pbc , rt -> flv_data , rt -> flv_size );
bytestream2_skip_p ( & pbc , old_flv_size );
bytestream2_put_byte ( & pbc , pkt -> type );
2013-09-21 11:09:39 +02:00
bytestream2_put_be24 ( & pbc , size );
2013-09-19 01:49:41 +02:00
bytestream2_put_be24 ( & pbc , ts );
bytestream2_put_byte ( & pbc , ts >> 24 );
bytestream2_put_be24 ( & pbc , 0 );
2013-09-21 11:09:39 +02:00
bytestream2_put_buffer ( & pbc , data , size );
2015-10-13 12:17:24 +03:00
bytestream2_put_be32 ( & pbc , size + RTMP_HEADER );
2013-09-19 01:49:41 +02:00
2012-08-16 16:04:48 +02:00
return 0 ;
}
2013-09-21 11:09:39 +02:00
static int handle_notify ( URLContext * s , RTMPPacket * pkt )
{
RTMPContext * rt = s -> priv_data ;
uint8_t commandbuffer [ 64 ];
char statusmsg [ 128 ];
int stringlen , ret , skip = 0 ;
GetByteContext gbc ;
bytestream2_init ( & gbc , pkt -> data , pkt -> size );
if ( ff_amf_read_string ( & gbc , commandbuffer , sizeof ( commandbuffer ),
& stringlen ))
return AVERROR_INVALIDDATA ;
2014-05-31 20:37:26 +01:00
if ( ! strcmp ( commandbuffer , "onMetaData" )) {
// metadata properties should be stored in a mixed array
if ( bytestream2_get_byte ( & gbc ) == AMF_DATA_TYPE_MIXEDARRAY ) {
// We have found a metaData Array so flv can determine the streams
// from this.
rt -> received_metadata = 1 ;
// skip 32-bit max array index
bytestream2_skip ( & gbc , 4 );
while ( bytestream2_get_bytes_left ( & gbc ) > 3 ) {
if ( ff_amf_get_string ( & gbc , statusmsg , sizeof ( statusmsg ),
& stringlen ))
return AVERROR_INVALIDDATA ;
// We do not care about the content of the property (yet).
stringlen = ff_amf_tag_size ( gbc . buffer , gbc . buffer_end );
if ( stringlen < 0 )
return AVERROR_INVALIDDATA ;
bytestream2_skip ( & gbc , stringlen );
// The presence of the following properties indicates that the
// respective streams are present.
if ( ! strcmp ( statusmsg , "videocodecid" )) {
rt -> has_video = 1 ;
}
if ( ! strcmp ( statusmsg , "audiocodecid" )) {
rt -> has_audio = 1 ;
}
}
if ( bytestream2_get_be24 ( & gbc ) != AMF_END_OF_OBJECT )
return AVERROR_INVALIDDATA ;
}
}
2013-09-21 11:09:39 +02:00
// Skip the @setDataFrame string and validate it is a notification
if ( ! strcmp ( commandbuffer , "@setDataFrame" )) {
skip = gbc . buffer - pkt -> data ;
ret = ff_amf_read_string ( & gbc , statusmsg ,
sizeof ( statusmsg ), & stringlen );
if ( ret < 0 )
return AVERROR_INVALIDDATA ;
}
return append_flv_data ( rt , pkt , skip );
}
2012-07-21 12:59:58 +02: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 ) {
2012-08-13 19:46:04 +02:00
case RTMP_PT_BYTES_READ :
2015-03-16 08:57:35 +00:00
av_log ( s , AV_LOG_TRACE , "received bytes read report \n " );
2012-08-13 19:46:04 +02:00
break ;
2012-07-21 12:59:58 +02:00
case RTMP_PT_CHUNK_SIZE :
if (( ret = handle_chunk_size ( s , pkt )) < 0 )
return ret ;
break ;
2017-01-31 15:47:00 +02:00
case RTMP_PT_USER_CONTROL :
if (( ret = handle_user_control ( s , pkt )) < 0 )
2012-07-21 12:59:58 +02:00
return ret ;
break ;
2017-01-31 15:47:00 +02:00
case RTMP_PT_SET_PEER_BW :
if (( ret = handle_set_peer_bw ( s , pkt )) < 0 )
2012-07-21 12:59:58 +02:00
return ret ;
break ;
2017-01-31 15:47:00 +02:00
case RTMP_PT_WINDOW_ACK_SIZE :
if (( ret = handle_window_ack_size ( s , pkt )) < 0 )
2012-07-21 12:59:58 +02:00
return ret ;
break ;
case RTMP_PT_INVOKE :
if (( ret = handle_invoke ( s , pkt )) < 0 )
return ret ;
2009-07-31 06:49:36 +00:00
break ;
2012-07-03 18:20:02 +00:00
case RTMP_PT_VIDEO :
case RTMP_PT_AUDIO :
2012-07-26 21:10:09 +02:00
case RTMP_PT_METADATA :
2012-08-16 16:04:48 +02:00
case RTMP_PT_NOTIFY :
2012-07-26 21:10:09 +02:00
/* Audio, Video and Metadata packets are parsed in get_packet() */
2012-07-03 18:20:02 +00:00
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 06:49:36 +00:00
}
return 0 ;
}
2013-09-21 11:10:41 +02:00
static int handle_metadata ( RTMPContext * rt , RTMPPacket * pkt )
{
int ret , old_flv_size , type ;
2026-04-18 09:38:08 -03:00
PutByteContext pbc ;
GetByteContext gbc ;
2013-09-21 11:10:41 +02:00
uint32_t size ;
uint32_t ts , cts , pts = 0 ;
old_flv_size = update_offset ( rt , pkt -> size );
2026-04-18 09:38:08 -03:00
if ( old_flv_size < 0 )
return old_flv_size ;
2013-09-21 11:10:41 +02:00
2013-10-03 12:27:10 +02:00
if (( ret = av_reallocp ( & rt -> flv_data , rt -> flv_size )) < 0 ) {
rt -> flv_size = rt -> flv_off = 0 ;
2013-09-21 11:10:41 +02:00
return ret ;
2013-10-03 12:27:10 +02:00
}
2013-09-21 11:10:41 +02:00
2026-04-18 09:38:08 -03:00
bytestream2_init ( & gbc , pkt -> data , pkt -> size );
bytestream2_init_writer ( & pbc , rt -> flv_data , rt -> flv_size );
bytestream2_skip_p ( & pbc , old_flv_size );
2013-09-21 11:10:41 +02:00
/* copy data while rewriting timestamps */
ts = pkt -> timestamp ;
2026-04-18 09:38:08 -03:00
while ( bytestream2_get_bytes_left ( & gbc ) > RTMP_HEADER ) {
type = bytestream2_get_byte ( & gbc );
size = bytestream2_get_be24 ( & gbc );
cts = bytestream2_get_be24 ( & gbc );
cts |= bytestream2_get_byte ( & gbc ) << 24 ;
2013-09-21 11:10:41 +02:00
if ( ! pts )
pts = cts ;
ts += cts - pts ;
pts = cts ;
2026-04-18 09:38:08 -03:00
if ( size + 3 + 4 > bytestream2_get_bytes_left ( & gbc ))
2013-10-03 13:49:50 +02:00
break ;
2026-04-18 09:38:08 -03:00
bytestream2_put_byte ( & pbc , type );
bytestream2_put_be24 ( & pbc , size );
bytestream2_put_be24 ( & pbc , ts );
bytestream2_put_byte ( & pbc , ts >> 24 );
bytestream2_copy_buffer ( & pbc , & gbc , size + 3 );
bytestream2_skip ( & gbc , 4 );
bytestream2_put_be32 ( & pbc , size + RTMP_HEADER );
2013-09-21 11:10:41 +02:00
}
2026-04-18 09:38:08 -03:00
if ( bytestream2_tell_p ( & pbc ) != rt -> flv_size ) {
2019-09-30 14:36:52 +08:00
av_log ( rt , AV_LOG_WARNING , "Incomplete flv packets in "
2013-10-03 13:44:38 +02:00
"RTMP_PT_METADATA packet \n " );
2026-04-18 09:38:08 -03:00
rt -> flv_size = bytestream2_tell_p ( & pbc );
2013-10-03 13:44:38 +02:00
}
2013-09-21 11:10:41 +02:00
return 0 ;
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Interact with the server by receiving and sending RTMP packets until
2009-07-31 06:49:36 +00:00
* there is some significant data (media data or expected status notification).
*
* @param s reading context
2009-11-12 23:05:56 +00: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 06:49:36 +00: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 ;
2009-12-11 11:37:21 +00:00
if ( rt -> state == STATE_STOPPED )
return AVERROR_EOF ;
2009-12-06 07:03:46 +00:00
for (;;) {
2011-05-25 19:08:29 +03:00
RTMPPacket rpkt = { 0 };
2009-07-31 06:49:36 +00:00
if (( ret = ff_rtmp_packet_read ( rt -> stream , & rpkt ,
2013-10-11 22:16:04 +03:00
rt -> in_chunk_size , & rt -> prev_pkt [ 0 ],
& rt -> nb_prev_pkt [ 0 ])) <= 0 ) {
2010-01-30 09:45:52 +00:00
if ( ret == 0 ) {
2009-07-31 06:49:36 +00:00
return AVERROR ( EAGAIN );
} else {
return AVERROR ( EIO );
}
}
2014-10-17 16:30:46 +02:00
// Track timestamp for later use
rt -> last_timestamp = rpkt . timestamp ;
2010-02-18 16:27:18 +00:00
rt -> bytes_read += ret ;
2017-01-31 16:15:56 +02:00
if ( rt -> bytes_read - rt -> last_bytes_read > rt -> receive_report_size ) {
2011-02-03 12:53:28 +01:00
av_log ( s , AV_LOG_DEBUG , "Sending bytes read report \n " );
2018-01-23 16:49:16 -08:00
if (( ret = gen_bytes_read ( s , rt , rpkt . timestamp + 1 )) < 0 ) {
ff_rtmp_packet_destroy ( & rpkt );
2012-05-23 22:45:03 +02:00
return ret ;
2018-01-23 16:49:16 -08:00
}
2010-02-18 16:27:18 +00:00
rt -> last_bytes_read = rt -> bytes_read ;
}
2009-07-31 06:49:36 +00:00
ret = rtmp_parse_result ( s , rt , & rpkt );
2013-08-02 12:29:23 +03:00
// At this point we must check if we are in the seek state and continue
// with the next packet. handle_invoke will get us out of this state
// when the right message is encountered
if ( rt -> state == STATE_SEEKING ) {
ff_rtmp_packet_destroy ( & rpkt );
// We continue, let the natural flow of things happen:
// AVERROR(EAGAIN) or handle_invoke gets us out of here
continue ;
}
2009-07-31 06:49:36 +00:00
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 06:49:36 +00:00
}
2012-12-30 22:39:38 +02:00
if ( rt -> do_reconnect && for_header ) {
ff_rtmp_packet_destroy ( & rpkt );
return 0 ;
}
2009-12-11 11:37:21 +00:00
if ( rt -> state == STATE_STOPPED ) {
ff_rtmp_packet_destroy ( & rpkt );
return AVERROR_EOF ;
}
2012-08-16 16:04:48 +02:00
if ( for_header && ( rt -> state == STATE_PLAYING ||
rt -> state == STATE_PUBLISHING ||
2013-09-15 16:52:33 +02:00
rt -> state == STATE_SENDING ||
2012-08-16 16:04:48 +02:00
rt -> state == STATE_RECEIVING )) {
2009-07-31 06:49:36 +00:00
ff_rtmp_packet_destroy ( & rpkt );
return 0 ;
}
2013-08-08 18:52:11 +02:00
if ( ! rpkt . size || ! rt -> is_input ) {
2009-07-31 06:49:36 +00:00
ff_rtmp_packet_destroy ( & rpkt );
continue ;
}
2013-09-21 11:09:39 +02:00
if ( rpkt . type == RTMP_PT_VIDEO || rpkt . type == RTMP_PT_AUDIO ) {
ret = append_flv_data ( rt , & rpkt , 0 );
2009-07-31 06:49:36 +00:00
ff_rtmp_packet_destroy ( & rpkt );
2013-09-21 11:09:39 +02:00
return ret ;
2012-08-16 16:04:48 +02:00
} else if ( rpkt . type == RTMP_PT_NOTIFY ) {
ret = handle_notify ( s , & rpkt );
ff_rtmp_packet_destroy ( & rpkt );
2013-09-21 11:09:39 +02:00
return ret ;
2009-07-31 06:49:36 +00:00
} else if ( rpkt . type == RTMP_PT_METADATA ) {
2013-09-21 11:10:41 +02:00
ret = handle_metadata ( rt , & rpkt );
2009-07-31 06:49:36 +00:00
ff_rtmp_packet_destroy ( & rpkt );
2017-07-06 13:59:40 -04:00
return ret ;
2009-07-31 06:49:36 +00:00
}
ff_rtmp_packet_destroy ( & rpkt );
}
}
static int rtmp_close ( URLContext * h )
{
RTMPContext * rt = h -> priv_data ;
2013-09-16 23:58:48 -07:00
int ret = 0 , i , j ;
2009-07-31 06:49:36 +00:00
2009-12-06 07:03:46 +00:00
if ( ! rt -> is_input ) {
2009-12-04 16:52:16 +00:00
rt -> flv_data = NULL ;
2013-08-08 18:52:11 +02:00
if ( rt -> out_pkt . size )
2009-12-04 16:52:16 +00:00
ff_rtmp_packet_destroy ( & rt -> out_pkt );
2009-12-06 07:01:37 +00:00
if ( rt -> state > STATE_FCPUBLISH )
2012-05-23 22:45:03 +02:00
ret = gen_fcunpublish_stream ( h , rt );
2009-12-04 16:52:16 +00:00
}
2009-12-06 07:01:37 +00:00
if ( rt -> state > STATE_HANDSHAKED )
2012-05-23 22:45:03 +02:00
ret = gen_delete_stream ( h , rt );
2013-10-11 22:16:04 +03:00
for ( i = 0 ; i < 2 ; i ++ ) {
for ( j = 0 ; j < rt -> nb_prev_pkt [ i ]; j ++ )
2013-09-16 23:58:48 -07:00
ff_rtmp_packet_destroy ( & rt -> prev_pkt [ i ][ j ]);
2013-10-11 22:16:04 +03:00
av_freep ( & rt -> prev_pkt [ i ]);
}
2009-12-04 16:52:16 +00:00
2012-08-08 14:36:39 +02:00
free_tracked_methods ( rt );
2009-07-31 06:49:36 +00:00
av_freep ( & rt -> flv_data );
2020-04-03 17:03:38 +02:00
ffurl_closep ( & rt -> stream );
2012-05-23 22:45:03 +02:00
return ret ;
2009-07-31 06:49:36 +00:00
}
2014-10-14 17:16:21 +02:00
/**
* Insert a fake onMetadata packet into the FLV stream to notify the FLV
* demuxer about the duration of the stream.
*
* This should only be done if there was no real onMetadata packet sent by the
* server at the start of the stream and if we were able to retrieve a valid
* duration via a getStreamLength call.
*
* @return 0 for successful operation, negative value in case of error
*/
static int inject_fake_duration_metadata ( RTMPContext * rt )
{
2016-04-27 13:45:23 -04:00
// We need to insert the metadata packet directly after the FLV
2014-10-14 17:16:21 +02:00
// header, i.e. we need to move all other already read data by the
// size of our fake metadata packet.
uint8_t * p ;
// Keep old flv_data pointer
uint8_t * old_flv_data = rt -> flv_data ;
// Allocate a new flv_data pointer with enough space for the additional package
if ( ! ( rt -> flv_data = av_malloc ( rt -> flv_size + 55 ))) {
rt -> flv_data = old_flv_data ;
return AVERROR ( ENOMEM );
}
// Copy FLV header
memcpy ( rt -> flv_data , old_flv_data , 13 );
// Copy remaining packets
memcpy ( rt -> flv_data + 13 + 55 , old_flv_data + 13 , rt -> flv_size - 13 );
// Increase the size by the injected packet
rt -> flv_size += 55 ;
// Delete the old FLV data
2014-12-22 11:52:22 +01:00
av_freep ( & old_flv_data );
2014-10-14 17:16:21 +02:00
p = rt -> flv_data + 13 ;
bytestream_put_byte ( & p , FLV_TAG_TYPE_META );
bytestream_put_be24 ( & p , 40 ); // size of data part (sum of all parts below)
bytestream_put_be24 ( & p , 0 ); // timestamp
bytestream_put_be32 ( & p , 0 ); // reserved
// first event name as a string
bytestream_put_byte ( & p , AMF_DATA_TYPE_STRING );
// "onMetaData" as AMF string
bytestream_put_be16 ( & p , 10 );
bytestream_put_buffer ( & p , "onMetaData" , 10 );
// mixed array (hash) with size and string/type/data tuples
bytestream_put_byte ( & p , AMF_DATA_TYPE_MIXEDARRAY );
bytestream_put_be32 ( & p , 1 ); // metadata_count
// "duration" as AMF string
bytestream_put_be16 ( & p , 8 );
bytestream_put_buffer ( & p , "duration" , 8 );
bytestream_put_byte ( & p , AMF_DATA_TYPE_NUMBER );
bytestream_put_be64 ( & p , av_double2int ( rt -> duration ));
// Finalise object
bytestream_put_be16 ( & p , 0 ); // Empty string
bytestream_put_byte ( & p , AMF_END_OF_OBJECT );
2015-10-13 12:17:24 +03:00
bytestream_put_be32 ( & p , 40 + RTMP_HEADER ); // size of data part (sum of all parts above)
2014-10-14 17:16:21 +02:00
return 0 ;
}
2009-07-31 06:49:36 +00:00
/**
2010-06-30 15:38:06 +00:00
* Open RTMP connection and verify that the stream can be played.
2009-07-31 06:49:36 +00: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:")
*/
2017-03-22 18:06:14 +08:00
static int rtmp_open ( URLContext * s , const char * uri , int flags , AVDictionary ** opts )
2009-07-31 06:49:36 +00:00
{
2011-12-01 11:44:21 +02:00
RTMPContext * rt = s -> priv_data ;
2012-12-30 22:39:38 +02:00
char proto [ 8 ], hostname [ 256 ], path [ 1024 ], auth [ 100 ], * fname ;
2014-10-19 18:16:17 +02:00
char * old_app , * qmark , * n , fname_buffer [ 1024 ];
2009-07-31 06:49:36 +00:00
uint8_t buf [ 2048 ];
2009-12-02 12:55:10 +00:00
int port ;
2009-07-31 06:49:36 +00:00
int ret ;
2012-08-16 16:04:48 +02:00
if ( rt -> listen_timeout > 0 )
rt -> listen = 1 ;
2011-04-15 16:42:09 +02:00
rt -> is_input = ! ( flags & AVIO_FLAG_WRITE );
2009-07-31 06:49:36 +00:00
2012-12-30 22:39:38 +02:00
av_url_split ( proto , sizeof ( proto ), auth , sizeof ( auth ),
hostname , sizeof ( hostname ), & port ,
2010-03-08 09:05:03 +00:00
path , sizeof ( path ), s -> filename );
2009-07-31 06:49:36 +00:00
2014-10-19 18:16:17 +02:00
n = strchr ( path , ' ' );
if ( n ) {
2013-08-10 11:53:31 +03:00
av_log ( s , AV_LOG_WARNING ,
"Detected librtmp style URL parameters, these aren't supported "
"by the libavformat internal RTMP handler currently enabled. "
"See the documentation for the correct way to pass parameters. \n " );
2014-10-19 18:16:17 +02:00
* n = '\0' ; // Trim not supported part
2013-08-10 11:53:31 +03:00
}
2012-12-30 22:39:38 +02:00
if ( auth [ 0 ]) {
char * ptr = strchr ( auth , ':' );
if ( ptr ) {
* ptr = '\0' ;
av_strlcpy ( rt -> username , auth , sizeof ( rt -> username ));
av_strlcpy ( rt -> password , ptr + 1 , sizeof ( rt -> password ));
}
}
2012-08-16 16:04:48 +02:00
if ( rt -> listen && strcmp ( proto , "rtmp" )) {
av_log ( s , AV_LOG_ERROR , "rtmp_listen not available for %s \n " ,
proto );
return AVERROR ( EINVAL );
}
2012-07-17 12:02:43 +02:00
if ( ! strcmp ( proto , "rtmpt" ) || ! strcmp ( proto , "rtmpts" )) {
if ( ! strcmp ( proto , "rtmpts" ))
2024-06-07 23:12:08 +02:00
av_dict_set ( opts , "ffrtmphttp_tls" , "1" , AV_DICT_MATCH_CASE );
2012-07-17 12:02:43 +02:00
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" ))
2017-03-22 18:06:14 +08:00
av_dict_set ( opts , "ffrtmpcrypt_tunneling" , "1" , 1 );
2012-07-20 16:36:47 +02:00
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 ;
2012-08-16 16:04:48 +02:00
if ( rt -> listen )
ff_url_join ( buf , sizeof ( buf ), "tcp" , NULL , hostname , port ,
2021-06-11 21:02:02 +02:00
"?listen&listen_timeout=%d&tcp_nodelay=%d" ,
2026-03-11 21:48:33 +08:00
rt -> listen_timeout < 0 ? - 1 : rt -> listen_timeout * 1000 ,
rt -> tcp_nodelay );
2012-08-16 16:04:48 +02:00
else
2021-06-11 21:02:02 +02:00
ff_url_join ( buf , sizeof ( buf ), "tcp" , NULL , hostname , port , "?tcp_nodelay=%d" , rt -> tcp_nodelay );
2012-06-17 20:24:43 +02:00
}
2009-07-31 06:49:36 +00:00
2012-12-30 22:39:38 +02:00
reconnect :
2016-01-30 02:17:51 +01:00
if (( ret = ffurl_open_whitelist ( & rt -> stream , buf , AVIO_FLAG_READ_WRITE ,
2017-03-22 18:06:14 +08:00
& s -> interrupt_callback , opts ,
2016-04-21 15:55:09 +01:00
s -> protocol_whitelist , s -> protocol_blacklist , s )) < 0 ) {
2011-04-15 16:42:09 +02:00
av_log ( s , AV_LOG_ERROR , "Cannot open connection %s \n " , buf );
2009-07-31 06:49:36 +00:00
goto fail ;
2009-11-22 08:42:55 +00:00
}
2009-07-31 06:49:36 +00:00
2012-08-15 16:11:50 +02:00
if ( rt -> swfverify ) {
if (( ret = rtmp_calc_swfhash ( s )) < 0 )
goto fail ;
}
2009-12-04 16:52:42 +00:00
rt -> state = STATE_START ;
2012-08-16 16:04:48 +02:00
if ( ! rt -> listen && ( ret = rtmp_handshake ( s , rt )) < 0 )
goto fail ;
if ( rt -> listen && ( ret = rtmp_server_handshake ( s , rt )) < 0 )
2011-12-01 11:34:06 +02:00
goto fail ;
2009-07-31 06:49:36 +00:00
2012-08-10 19:03:22 +02:00
rt -> out_chunk_size = 128 ;
rt -> in_chunk_size = 128 ; // Probably overwritten later
2009-12-04 16:52:42 +00:00
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 16:52:42 +00:00
//extract "app" part from path
2014-05-05 21:47:05 +01:00
qmark = strchr ( path , '?' );
if ( qmark && strstr ( qmark , "slist=" )) {
char * amp ;
2015-11-11 22:38:39 +02:00
// After slist we have the playpath, the full path is used as app
av_strlcpy ( rt -> app , path + 1 , APP_MAX_LENGTH );
2014-05-05 21:47:05 +01:00
fname = strstr ( path , "slist=" ) + 6 ;
// Strip any further query parameters from fname
amp = strchr ( fname , '&' );
if ( amp ) {
2014-05-08 15:12:23 +03:00
av_strlcpy ( fname_buffer , fname , FFMIN ( amp - fname + 1 ,
sizeof ( fname_buffer )));
2014-05-05 21:47:05 +01:00
fname = fname_buffer ;
}
} else if ( ! strncmp ( path , "/ondemand/" , 10 )) {
2009-12-04 16:52:42 +00:00
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 16:52:42 +00:00
if ( ! p ) {
2014-10-19 18:16:17 +02:00
if ( old_app ) {
// If name of application has been defined by the user, assume that
// playpath is provided in the URL
fname = next ;
} else {
fname = NULL ;
av_strlcpy ( rt -> app , next , APP_MAX_LENGTH );
}
2009-07-31 06:49:36 +00:00
} else {
2012-05-14 17:24:27 -07:00
// make sure we do not mismatch a playpath for an application instance
2009-12-04 16:52:42 +00:00
char * c = strchr ( p + 1 , ':' );
fname = strchr ( p + 1 , '/' );
2012-05-14 17:24:27 -07:00
if ( ! fname || ( c && c < fname )) {
2009-12-04 16:52:42 +00:00
fname = p + 1 ;
2013-02-23 16:58:01 +01:00
av_strlcpy ( rt -> app , path + 1 , FFMIN ( p - path , APP_MAX_LENGTH ));
2009-07-31 06:49:36 +00:00
} else {
2009-12-04 16:52:42 +00:00
fname ++ ;
2013-02-23 16:58:01 +01:00
av_strlcpy ( rt -> app , path + 1 , FFMIN ( fname - path - 1 , APP_MAX_LENGTH ));
2009-07-31 06:49:36 +00:00
}
}
2009-12-04 16:52:42 +00:00
}
2012-04-15 21:49:48 +02:00
if ( old_app ) {
// The name of application has been defined by the user, override it.
2013-02-23 16:58:01 +01:00
if ( strlen ( old_app ) >= APP_MAX_LENGTH ) {
ret = AVERROR ( EINVAL );
goto fail ;
}
2012-04-15 21:49:48 +02:00
av_free ( rt -> app );
rt -> app = old_app ;
}
2012-04-15 21:50:50 +02:00
if ( ! rt -> playpath ) {
2019-11-15 19:10:47 +08:00
int max_len = 1 ;
if ( fname )
max_len = strlen ( fname ) + 5 ; // add prefix "mp4:"
rt -> playpath = av_malloc ( max_len );
2012-04-15 21:50:50 +02:00
if ( ! rt -> playpath ) {
2012-05-23 22:45:03 +02:00
ret = AVERROR ( ENOMEM );
goto fail ;
2012-04-15 21:50:50 +02:00
}
2014-10-19 18:16:17 +02:00
if ( fname ) {
int len = strlen ( fname );
if ( ! strchr ( fname , ':' ) && len >= 4 &&
( ! strcmp ( fname + len - 4 , ".f4v" ) ||
! strcmp ( fname + len - 4 , ".mp4" ))) {
memcpy ( rt -> playpath , "mp4:" , 5 );
} else {
if ( len >= 4 && ! strcmp ( fname + len - 4 , ".flv" ))
fname [ len - 4 ] = '\0' ;
rt -> playpath [ 0 ] = 0 ;
}
2019-11-15 19:10:47 +08:00
av_strlcat ( rt -> playpath , fname , max_len );
2012-04-15 21:50:50 +02:00
} else {
2014-10-19 18:16:17 +02:00
rt -> playpath [ 0 ] = '\0' ;
2012-04-15 21:50:50 +02:00
}
2009-12-04 16:52:42 +00:00
}
2009-07-31 06:49:36 +00: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 );
}
}
2025-10-30 23:05:57 +01:00
if ( strlen ( rt -> flashver ) > FLASHVER_MAX_LENGTH
|| strlen ( rt -> tcurl ) > TCURL_MAX_LENGTH
) {
ret = AVERROR ( EINVAL );
goto fail ;
}
2012-05-09 02:12:15 +02:00
2017-01-31 16:15:56 +02:00
rt -> receive_report_size = 1048576 ;
2010-02-18 16:27:18 +00:00
rt -> bytes_read = 0 ;
2014-05-31 20:37:26 +01:00
rt -> has_audio = 0 ;
rt -> has_video = 0 ;
rt -> received_metadata = 0 ;
2010-02-18 16:27:18 +00:00
rt -> last_bytes_read = 0 ;
2017-01-31 16:15:56 +02:00
rt -> max_sent_unacked = 2500000 ;
2014-10-14 17:16:21 +02:00
rt -> duration = 0 ;
2010-02-18 16:27:18 +00: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 16:52:42 +00:00
proto , path , rt -> app , rt -> playpath );
2012-08-16 16:04:48 +02:00
if ( ! rt -> listen ) {
if (( ret = gen_connect ( s , rt )) < 0 )
goto fail ;
} else {
2014-04-13 13:44:03 +03:00
if (( ret = read_connect ( s , s -> priv_data )) < 0 )
2012-08-16 16:04:48 +02:00
goto fail ;
}
2009-07-31 06:49:36 +00:00
2009-12-04 16:52:42 +00:00
do {
ret = get_packet ( s , 1 );
2013-09-17 15:07:10 +03:00
} while ( ret == AVERROR ( EAGAIN ));
2009-12-04 16:52:42 +00:00
if ( ret < 0 )
goto fail ;
2009-12-04 16:52:16 +00:00
2012-12-30 22:39:38 +02:00
if ( rt -> do_reconnect ) {
2013-10-11 22:16:04 +03:00
int i ;
2020-04-03 17:03:38 +02:00
ffurl_closep ( & rt -> stream );
2012-12-30 22:39:38 +02:00
rt -> do_reconnect = 0 ;
rt -> nb_invokes = 0 ;
2013-10-11 22:16:04 +03:00
for ( i = 0 ; i < 2 ; i ++ )
memset ( rt -> prev_pkt [ i ], 0 ,
sizeof ( ** rt -> prev_pkt ) * rt -> nb_prev_pkt [ i ]);
2012-12-30 22:39:38 +02:00
free_tracked_methods ( rt );
goto reconnect ;
}
2009-12-04 16:52:16 +00:00
if ( rt -> is_input ) {
2009-07-31 06:49:36 +00:00
// generate FLV header for demuxer
rt -> flv_size = 13 ;
2014-10-18 16:02:32 +02:00
if (( ret = av_reallocp ( & rt -> flv_data , rt -> flv_size )) < 0 )
goto fail ;
2009-07-31 06:49:36 +00:00
rt -> flv_off = 0 ;
2014-05-31 20:37:26 +01:00
memcpy ( rt -> flv_data , "FLV \1\0\0\0\0\011\0\0\0\0 " , rt -> flv_size );
// Read packets until we reach the first A/V packet or read metadata.
// If there was a metadata package in front of the A/V packets, we can
// build the FLV header from this. If we do not receive any metadata,
// the FLV decoder will allocate the needed streams when their first
// audio or video packet arrives.
while ( ! rt -> has_audio && ! rt -> has_video && ! rt -> received_metadata ) {
if (( ret = get_packet ( s , 0 )) < 0 )
2014-09-26 09:45:08 +11:00
goto fail ;
2014-05-31 20:37:26 +01:00
}
// Either after we have read the metadata or (if there is none) the
// first packet of an A/V stream, we have a better knowledge about the
// streams, so set the FLV header accordingly.
if ( rt -> has_audio ) {
rt -> flv_data [ 4 ] |= FLV_HEADER_FLAG_HASAUDIO ;
}
if ( rt -> has_video ) {
rt -> flv_data [ 4 ] |= FLV_HEADER_FLAG_HASVIDEO ;
}
2014-10-14 17:16:21 +02:00
// If we received the first packet of an A/V stream and no metadata but
// the server returned a valid duration, create a fake metadata packet
// to inform the FLV decoder about the duration.
if ( ! rt -> received_metadata && rt -> duration > 0 ) {
if (( ret = inject_fake_duration_metadata ( rt )) < 0 )
goto fail ;
}
2009-12-04 16:52:16 +00: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 06:49:36 +00:00
}
2011-03-31 17:58:04 +02:00
s -> max_packet_size = rt -> stream -> max_packet_size ;
2009-07-31 06:49:36 +00:00
s -> is_streamed = 1 ;
return 0 ;
fail :
rtmp_close ( s );
2012-05-23 22:45:03 +02:00
return ret ;
2009-07-31 06:49:36 +00: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 12:02:51 +00:00
return data_left ;
2009-07-31 06:49:36 +00:00
}
if (( ret = get_packet ( s , 0 )) < 0 )
return ret ;
}
return orig_size ;
}
2024-03-02 19:29:27 +01:00
static int64_t rtmp_seek ( void * opaque , int stream_index , int64_t timestamp ,
2013-08-02 12:29:23 +03:00
int flags )
{
2024-03-02 19:29:27 +01:00
URLContext * s = opaque ;
2013-08-02 12:29:23 +03:00
RTMPContext * rt = s -> priv_data ;
int ret ;
av_log ( s , AV_LOG_DEBUG ,
2013-08-08 17:43:15 +02:00
"Seek on stream index %d at timestamp %" PRId64 " with flags %08x \n " ,
2013-08-02 12:29:23 +03:00
stream_index , timestamp , flags );
if (( ret = gen_seek ( s , rt , timestamp )) < 0 ) {
av_log ( s , AV_LOG_ERROR ,
2013-08-08 17:43:15 +02:00
"Unable to send seek command on stream index %d at timestamp "
"%" PRId64 " with flags %08x \n " ,
2013-08-02 12:29:23 +03:00
stream_index , timestamp , flags );
return ret ;
}
rt -> flv_off = rt -> flv_size ;
rt -> state = STATE_SEEKING ;
return timestamp ;
}
2024-03-02 19:29:27 +01:00
static int rtmp_pause ( void * opaque , int pause )
2014-10-17 16:30:47 +02:00
{
2024-03-02 19:29:27 +01:00
URLContext * s = opaque ;
2014-10-17 16:30:47 +02:00
RTMPContext * rt = s -> priv_data ;
int ret ;
av_log ( s , AV_LOG_DEBUG , "Pause at timestamp %d \n " ,
rt -> last_timestamp );
if (( ret = gen_pause ( s , rt , pause , rt -> last_timestamp )) < 0 ) {
av_log ( s , AV_LOG_ERROR , "Unable to send pause command at timestamp %d \n " ,
rt -> last_timestamp );
return ret ;
}
return 0 ;
}
2011-02-03 11:17:35 +00:00
static int rtmp_write ( URLContext * s , const uint8_t * buf , int size )
2009-07-31 06:49:36 +00:00
{
2011-02-03 11:17:35 +00:00
RTMPContext * rt = s -> priv_data ;
2009-12-04 16:52:16 +00:00
int size_temp = size ;
2014-11-24 10:17:20 +02:00
int pktsize , pkttype , copy ;
2009-12-04 16:52:16 +00:00
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 16:52:16 +00: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 ;
}
2009-12-04 16:52:16 +00:00
2013-09-21 21:16:04 +02:00
if ( rt -> flv_header_bytes < RTMP_HEADER ) {
2011-09-21 23:21:30 +03:00
const uint8_t * header = rt -> flv_header ;
2013-09-16 13:20:57 -07:00
int channel = RTMP_AUDIO_CHANNEL ;
2014-11-05 17:55:34 -08:00
2014-11-24 10:17:20 +02:00
copy = FFMIN ( RTMP_HEADER - rt -> flv_header_bytes , size_temp );
2011-09-21 23:21:30 +03:00
bytestream_get_buffer ( & buf_temp , rt -> flv_header + rt -> flv_header_bytes , copy );
rt -> flv_header_bytes += copy ;
size_temp -= copy ;
2013-09-21 21:16:04 +02:00
if ( rt -> flv_header_bytes < RTMP_HEADER )
2011-09-21 23:21:30 +03:00
break ;
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 16:52:16 +00:00
rt -> flv_size = pktsize ;
2013-09-16 13:20:56 -07:00
if ( pkttype == RTMP_PT_VIDEO )
channel = RTMP_VIDEO_CHANNEL ;
2009-12-04 16:52:16 +00:00
if ((( pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO ) && ts == 0 ) ||
pkttype == RTMP_PT_NOTIFY ) {
2013-10-11 22:16:04 +03:00
if (( ret = ff_rtmp_check_alloc_array ( & rt -> prev_pkt [ 1 ],
& rt -> nb_prev_pkt [ 1 ],
channel )) < 0 )
return ret ;
2014-11-28 19:39:38 +02:00
// Force sending a full 12 bytes header by clearing the
2014-11-24 10:02:11 +02:00
// channel id, to make it not match a potential earlier
// packet in the same channel.
2013-09-16 13:20:56 -07:00
rt -> prev_pkt [ 1 ][ channel ]. channel_id = 0 ;
2009-12-04 16:52:16 +00:00
}
//this can be a big packet, it's better to send it right here
2013-09-16 13:20:56 -07:00
if (( ret = ff_rtmp_packet_create ( & rt -> out_pkt , channel ,
2012-05-23 22:45:03 +02:00
pkttype , ts , pktsize )) < 0 )
return ret ;
2022-03-31 16:23:17 +02:00
// If rt->listen, then we're running as a a server and should
// use the ID that we've sent in Stream Begin and in the
// _result to createStream.
// Otherwise, we're running as a client and should use the ID
// that we've received in the createStream from the server.
rt -> out_pkt . extra = ( rt -> listen ) ? rt -> nb_streamid : rt -> stream_id ;
2009-12-04 16:52:16 +00:00
rt -> flv_data = rt -> out_pkt . data ;
}
2014-11-24 10:17:20 +02:00
copy = FFMIN ( rt -> flv_size - rt -> flv_off , size_temp );
bytestream_get_buffer ( & buf_temp , rt -> flv_data + rt -> flv_off , copy );
rt -> flv_off += copy ;
size_temp -= copy ;
2009-12-04 16:52:16 +00:00
if ( rt -> flv_off == rt -> flv_size ) {
2011-09-21 23:21:30 +03:00
rt -> skip_bytes = 4 ;
2014-11-24 10:51:46 +02:00
if ( rt -> out_pkt . type == RTMP_PT_NOTIFY ) {
// For onMetaData and |RtmpSampleAccess packets, we want
// @setDataFrame prepended to the packet before it gets sent.
// However, not all RTMP_PT_NOTIFY packets (e.g., onTextData
// and onCuePoint).
uint8_t commandbuffer [ 64 ];
int stringlen = 0 ;
GetByteContext gbc ;
bytestream2_init ( & gbc , rt -> flv_data , rt -> flv_size );
if ( ! ff_amf_read_string ( & gbc , commandbuffer , sizeof ( commandbuffer ),
& stringlen )) {
if ( ! strcmp ( commandbuffer , "onMetaData" ) ||
! strcmp ( commandbuffer , "|RtmpSampleAccess" )) {
uint8_t * ptr ;
if (( ret = av_reallocp ( & rt -> out_pkt . data , rt -> out_pkt . size + 16 )) < 0 ) {
rt -> flv_size = rt -> flv_off = rt -> flv_header_bytes = 0 ;
return ret ;
}
memmove ( rt -> out_pkt . data + 16 , rt -> out_pkt . data , rt -> out_pkt . size );
rt -> out_pkt . size += 16 ;
ptr = rt -> out_pkt . data ;
ff_amf_write_string ( & ptr , "@setDataFrame" );
}
}
}
2012-08-08 14:36:39 +02:00
if (( ret = rtmp_send_packet ( rt , & rt -> out_pkt , 0 )) < 0 )
2012-05-24 13:48:25 +02:00
return ret ;
2009-12-04 16:52:16 +00: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 14:55:55 +02:00
rt -> flv_nb_packets ++ ;
2009-12-04 16:52:16 +00: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 ,
2012-08-10 19:03:22 +02:00
rt -> in_chunk_size ,
2013-10-11 22:16:04 +03:00
& rt -> prev_pkt [ 0 ],
& rt -> nb_prev_pkt [ 0 ], c )) <= 0 )
2012-06-14 15:28:40 +02:00
return ret ;
if (( ret = rtmp_parse_result ( s , rt , & rpkt )) < 0 )
return ret ;
ff_rtmp_packet_destroy ( & rpkt );
}
2009-12-04 16:52:16 +00:00
return size ;
2009-07-31 06:49:36 +00: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-08-31 13:22:31 +03:00
{ "rtmp_buffer" , "Set buffer time in milliseconds. The default is 3000." , OFFSET ( client_buffer_time ), AV_OPT_TYPE_INT , {. i64 = 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-08-31 13:22:31 +03:00
{ "rtmp_flush_interval" , "Number of packets flushed in the same request (RTMPT only)." , OFFSET ( flush_interval ), AV_OPT_TYPE_INT , {. i64 = 10 }, 0 , INT_MAX , ENC },
2023-08-28 09:59:24 +08:00
{ "rtmp_enhanced_codecs" , "Specify the codec(s) to use in an enhanced rtmp live stream" , OFFSET ( enhanced_codecs ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , ENC },
2024-02-11 15:41:05 +01:00
{ "rtmp_live" , "Specify that the media is a live stream." , OFFSET ( live ), AV_OPT_TYPE_INT , {. i64 = - 2 }, INT_MIN , INT_MAX , DEC , . unit = "rtmp_live" },
{ "any" , "both" , 0 , AV_OPT_TYPE_CONST , {. i64 = - 2 }, 0 , 0 , DEC , . unit = "rtmp_live" },
{ "live" , "live stream" , 0 , AV_OPT_TYPE_CONST , {. i64 = - 1 }, 0 , 0 , DEC , . unit = "rtmp_live" },
{ "recorded" , "recorded stream" , 0 , AV_OPT_TYPE_CONST , {. i64 = 0 }, 0 , 0 , DEC , . unit = "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-08-07 22:02:27 +02: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-08-13 17:05:00 +02:00
{ "rtmp_swfhash" , "SHA256 hash of the decompressed SWF file (32 bytes)." , OFFSET ( swfhash ), AV_OPT_TYPE_BINARY , . flags = DEC },
2012-08-31 13:22:31 +03:00
{ "rtmp_swfsize" , "Size of the decompressed SWF file, required for SWFVerification." , OFFSET ( swfsize ), AV_OPT_TYPE_INT , {. i64 = 0 }, 0 , INT_MAX , DEC },
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-08-15 16:11:50 +02:00
{ "rtmp_swfverify" , "URL to player swf file, compute hash/size automatically." , OFFSET ( swfverify ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC },
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 },
2024-02-11 15:41:05 +01:00
{ "rtmp_listen" , "Listen for incoming rtmp connections" , OFFSET ( listen ), AV_OPT_TYPE_INT , {. i64 = 0 }, INT_MIN , INT_MAX , DEC , . unit = "rtmp_listen" },
{ "listen" , "Listen for incoming rtmp connections" , OFFSET ( listen ), AV_OPT_TYPE_INT , {. i64 = 0 }, INT_MIN , INT_MAX , DEC , . unit = "rtmp_listen" },
2021-06-11 21:02:02 +02:00
{ "tcp_nodelay" , "Use TCP_NODELAY to disable Nagle's algorithm" , OFFSET ( tcp_nodelay ), AV_OPT_TYPE_INT , {. i64 = 0 }, 0 , 1 , DEC | ENC },
2024-02-11 15:41:05 +01:00
{ "timeout" , "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1" , OFFSET ( listen_timeout ), AV_OPT_TYPE_INT , {. i64 = - 1 }, INT_MIN , INT_MAX , DEC , . unit = "rtmp_listen" },
2012-04-15 21:49:48 +02:00
{ NULL },
};
2021-01-29 01:53:04 +01:00
#define RTMP_PROTOCOL_0(flavor)
#define RTMP_PROTOCOL_1(flavor) \
2012-08-07 12:21:31 +02:00
static const AVClass flavor##_class = { \
.class_name = #flavor, \
2024-01-19 13:33:28 +01:00
.item_name = av_default_item_name, \
2012-08-07 12:21:31 +02:00
.option = rtmp_options, \
.version = LIBAVUTIL_VERSION_INT, \
}; \
\
2016-02-19 10:39:29 +01:00
const URLProtocol ff_##flavor##_protocol = { \
2012-08-07 12:21:31 +02:00
.name = #flavor, \
2017-03-22 18:06:14 +08:00
.url_open2 = rtmp_open, \
2012-08-07 12:21:31 +02:00
.url_read = rtmp_read, \
2013-08-02 12:29:23 +03:00
.url_read_seek = rtmp_seek, \
2014-10-17 16:30:47 +02:00
.url_read_pause = rtmp_pause, \
2012-08-07 12:21:31 +02:00
.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 21:49:48 +02:00
};
2021-01-29 01:53:04 +01:00
#define RTMP_PROTOCOL_2(flavor, enabled) \
RTMP_PROTOCOL_ ## enabled(flavor)
#define RTMP_PROTOCOL_3(flavor, config) \
RTMP_PROTOCOL_2(flavor, config)
#define RTMP_PROTOCOL(flavor, uppercase) \
RTMP_PROTOCOL_3(flavor, CONFIG_ ## uppercase ## _PROTOCOL)
2012-04-15 21:49:48 +02:00
2021-01-29 01:53:04 +01:00
RTMP_PROTOCOL ( rtmp , RTMP )
RTMP_PROTOCOL ( rtmpe , RTMPE )
RTMP_PROTOCOL ( rtmps , RTMPS )
RTMP_PROTOCOL ( rtmpt , RTMPT )
RTMP_PROTOCOL ( rtmpte , RTMPTE )
RTMP_PROTOCOL ( rtmpts , RTMPTS )