2010-03-28 13:39:36 +00:00
/*
* RTMP network protocol
* Copyright (c) 2010 Howard Chu
*
* 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
2010-04-13 22:08:36 +00:00
* RTMP protocol based on http://rtmpdump.mplayerhq.hu/ librtmp
2010-03-28 13:39:36 +00:00
*/
2012-06-03 10:07:54 +02:00
#include "libavutil/avstring.h"
2022-04-15 07:53:01 -04:00
#include "libavutil/bprint.h"
2011-07-05 00:42:31 +02:00
#include "libavutil/mathematics.h"
2012-06-03 10:07:54 +02:00
#include "libavutil/opt.h"
2010-03-28 13:39:36 +00:00
#include "avformat.h"
2015-01-23 17:45:35 +01:00
#if CONFIG_NETWORK
2015-01-19 21:01:00 -06:00
#include "network.h"
2015-01-23 17:45:35 +01:00
#endif
2011-04-07 20:25:52 +02:00
#include "url.h"
2010-03-28 13:39:36 +00:00
#include <librtmp/rtmp.h>
#include <librtmp/log.h>
2012-06-03 10:07:54 +02:00
typedef struct LibRTMPContext {
const AVClass * class ;
2022-04-15 07:53:01 -04:00
AVBPrint filename ;
2012-06-03 10:07:54 +02:00
RTMP rtmp ;
char * app ;
2014-04-26 13:32:03 +02:00
char * conn ;
char * subscribe ;
2012-06-03 10:07:54 +02:00
char * playpath ;
2014-04-26 13:32:03 +02:00
char * tcurl ;
char * flashver ;
char * swfurl ;
char * swfverify ;
char * pageurl ;
char * client_buffer_time ;
int live ;
2015-01-19 21:01:00 -06:00
int buffer_size ;
2012-06-03 10:07:54 +02:00
} LibRTMPContext ;
2010-04-13 22:11:21 +00:00
static void rtmp_log ( int level , const char * fmt , va_list args )
{
switch ( level ) {
default :
case RTMP_LOGCRIT : level = AV_LOG_FATAL ; break ;
case RTMP_LOGERROR : level = AV_LOG_ERROR ; break ;
case RTMP_LOGWARNING : level = AV_LOG_WARNING ; break ;
case RTMP_LOGINFO : level = AV_LOG_INFO ; break ;
case RTMP_LOGDEBUG : level = AV_LOG_VERBOSE ; break ;
case RTMP_LOGDEBUG2 : level = AV_LOG_DEBUG ; break ;
}
av_vlog ( NULL , level , fmt , args );
av_log ( NULL , level , " \n " );
}
2010-03-28 13:39:36 +00:00
static int rtmp_close ( URLContext * s )
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2010-03-28 13:39:36 +00:00
RTMP_Close ( r );
2022-04-15 07:53:01 -04:00
av_bprint_finalize ( & ctx -> filename , NULL );
2010-03-28 13:39:36 +00:00
return 0 ;
}
/**
2010-06-30 15:38:06 +00:00
* Open RTMP connection and verify that the stream can be played.
2010-03-28 13:39:36 +00:00
*
* URL syntax: rtmp://server[:port][/app][/playpath][ keyword=value]...
* 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:")
*
* Additional RTMP library options may be appended as
* space-separated key-value pairs.
*/
static int rtmp_open ( URLContext * s , const char * uri , int flags )
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2012-06-03 10:09:03 +02:00
int rc = 0 , level ;
2022-04-15 07:53:01 -04:00
/* This needs to stay allocated for as long as the RTMP context exists. */
av_bprint_init ( & ctx -> filename , 0 , AV_BPRINT_SIZE_UNLIMITED );
2010-03-28 13:39:36 +00:00
2010-04-13 22:08:36 +00:00
switch ( av_log_get_level ()) {
2010-03-28 13:39:36 +00:00
default :
2012-06-03 10:09:03 +02:00
case AV_LOG_FATAL : level = RTMP_LOGCRIT ; break ;
case AV_LOG_ERROR : level = RTMP_LOGERROR ; break ;
case AV_LOG_WARNING : level = RTMP_LOGWARNING ; break ;
case AV_LOG_INFO : level = RTMP_LOGINFO ; break ;
case AV_LOG_VERBOSE : level = RTMP_LOGDEBUG ; break ;
case AV_LOG_DEBUG : level = RTMP_LOGDEBUG2 ; break ;
2010-03-28 13:39:36 +00:00
}
2012-06-03 10:09:03 +02:00
RTMP_LogSetLevel ( level );
2010-04-13 22:11:21 +00:00
RTMP_LogSetCallback ( rtmp_log );
2010-03-28 13:39:36 +00:00
2022-04-15 07:53:01 -04:00
av_bprintf ( & ctx -> filename , "%s" , s -> filename );
if ( ctx -> app )
av_bprintf ( & ctx -> filename , " app=%s" , ctx -> app );
if ( ctx -> tcurl )
av_bprintf ( & ctx -> filename , " tcUrl=%s" , ctx -> tcurl );
if ( ctx -> pageurl )
av_bprintf ( & ctx -> filename , " pageUrl=%s" , ctx -> pageurl );
if ( ctx -> swfurl )
av_bprintf ( & ctx -> filename , " swfUrl=%s" , ctx -> swfurl );
if ( ctx -> flashver )
av_bprintf ( & ctx -> filename , " flashVer=%s" , ctx -> flashver );
2014-04-26 13:32:03 +02:00
if ( ctx -> conn ) {
char * sep , * p = ctx -> conn ;
while ( p ) {
2022-04-15 07:53:01 -04:00
av_bprintf ( & ctx -> filename , " conn=" );
2014-04-26 13:32:03 +02:00
p += strspn ( p , " " );
if ( !* p )
break ;
sep = strchr ( p , ' ' );
if ( sep )
* sep = '\0' ;
2022-04-15 07:53:01 -04:00
av_bprintf ( & ctx -> filename , "%s" , p );
2014-04-26 13:32:03 +02:00
if ( sep )
p = sep + 1 ;
2016-06-22 06:36:31 +02:00
else
break ;
2014-04-26 13:32:03 +02:00
}
}
2022-04-15 07:53:01 -04:00
if ( ctx -> playpath )
av_bprintf ( & ctx -> filename , " playpath=%s" , ctx -> playpath );
2014-04-26 13:32:03 +02:00
if ( ctx -> live )
2022-04-15 07:53:01 -04:00
av_bprintf ( & ctx -> filename , " live=1" );
if ( ctx -> subscribe )
av_bprintf ( & ctx -> filename , " subscribe=%s" , ctx -> subscribe );
if ( ctx -> client_buffer_time )
av_bprintf ( & ctx -> filename , " buffer=%s" , ctx -> client_buffer_time );
2014-04-26 13:32:03 +02:00
if ( ctx -> swfurl || ctx -> swfverify ) {
2022-04-15 07:53:01 -04:00
if ( ctx -> swfverify )
av_bprintf ( & ctx -> filename , " swfUrl=%s swfVfy=1" , ctx -> swfverify );
else
av_bprintf ( & ctx -> filename , " swfUrl=%s" , ctx -> swfurl );
}
2014-04-26 13:32:03 +02:00
2022-04-15 07:53:01 -04:00
if ( ! av_bprint_is_complete ( & ctx -> filename )) {
av_bprint_finalize ( & ctx -> filename , NULL );
return AVERROR ( ENOMEM );
2012-06-03 10:07:54 +02:00
}
2010-03-28 13:39:36 +00:00
RTMP_Init ( r );
2022-04-15 07:53:01 -04:00
/* This will modify filename by null terminating the URL portion */
if ( ! RTMP_SetupURL ( r , ctx -> filename . str )) {
2012-06-03 10:11:43 +02:00
rc = AVERROR_UNKNOWN ;
2010-03-28 13:39:36 +00:00
goto fail ;
}
2011-04-15 16:42:09 +02:00
if ( flags & AVIO_FLAG_WRITE )
2010-07-01 10:59:44 +00:00
RTMP_EnableWrite ( r );
2010-03-28 13:39:36 +00:00
if ( ! RTMP_Connect ( r , NULL ) || ! RTMP_ConnectStream ( r , 0 )) {
2012-06-03 10:11:43 +02:00
rc = AVERROR_UNKNOWN ;
2010-03-28 13:39:36 +00:00
goto fail ;
}
2015-01-23 17:45:35 +01:00
#if CONFIG_NETWORK
2015-01-19 21:01:00 -06:00
if ( ctx -> buffer_size >= 0 && ( flags & AVIO_FLAG_WRITE )) {
int tmp = ctx -> buffer_size ;
2017-06-11 15:08:43 +02:00
if ( setsockopt ( r -> m_sb . sb_socket , SOL_SOCKET , SO_SNDBUF , & tmp , sizeof ( tmp ))) {
rc = AVERROR_EXTERNAL ;
goto fail ;
}
2015-01-19 21:01:00 -06:00
}
2015-01-23 17:45:35 +01:00
#endif
2015-01-19 21:01:00 -06:00
2010-03-28 13:39:36 +00:00
s -> is_streamed = 1 ;
2014-07-04 22:13:39 +03:00
return 0 ;
2010-03-28 13:39:36 +00:00
fail :
2022-04-15 07:53:01 -04:00
2014-01-14 01:42:42 +01:00
if ( rc )
RTMP_Close ( r );
2022-04-15 07:53:01 -04:00
av_bprint_finalize ( & ctx -> filename , NULL );
2014-01-14 01:42:42 +01:00
2010-03-28 13:39:36 +00:00
return rc ;
}
2010-06-01 07:46:23 +00:00
static int rtmp_write ( URLContext * s , const uint8_t * buf , int size )
2010-03-28 13:39:36 +00:00
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2010-03-28 13:39:36 +00:00
2018-07-26 00:37:35 +02:00
int ret = RTMP_Write ( r , buf , size );
if ( ! ret )
return AVERROR_EOF ;
return ret ;
2010-03-28 13:39:36 +00:00
}
static int rtmp_read ( URLContext * s , uint8_t * buf , int size )
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2010-03-28 13:39:36 +00:00
2018-07-26 00:37:35 +02:00
int ret = RTMP_Read ( r , buf , size );
if ( ! ret )
return AVERROR_EOF ;
return ret ;
2010-03-28 13:39:36 +00:00
}
static int rtmp_read_pause ( URLContext * s , int pause )
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2010-03-28 13:39:36 +00:00
2010-07-01 10:59:44 +00:00
if ( ! RTMP_Pause ( r , pause ))
2012-06-03 10:11:43 +02:00
return AVERROR_UNKNOWN ;
2010-03-28 13:39:36 +00:00
return 0 ;
}
static int64_t rtmp_read_seek ( URLContext * s , int stream_index ,
int64_t timestamp , int flags )
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2010-03-28 13:39:36 +00:00
if ( flags & AVSEEK_FLAG_BYTE )
2010-04-18 19:09:22 +00:00
return AVERROR ( ENOSYS );
2010-03-28 13:39:36 +00:00
/* seeks are in milliseconds */
2010-04-18 19:09:25 +00:00
if ( stream_index < 0 )
timestamp = av_rescale_rnd ( timestamp , 1000 , AV_TIME_BASE ,
flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP );
2010-03-28 13:39:36 +00:00
if ( ! RTMP_SendSeek ( r , timestamp ))
2012-06-03 10:11:43 +02:00
return AVERROR_UNKNOWN ;
2010-03-28 13:39:36 +00:00
return timestamp ;
}
static int rtmp_get_file_handle ( URLContext * s )
{
2012-06-03 10:07:54 +02:00
LibRTMPContext * ctx = s -> priv_data ;
RTMP * r = & ctx -> rtmp ;
2010-03-28 13:39:36 +00:00
2010-07-01 10:59:44 +00:00
return RTMP_Socket ( r );
2010-03-28 13:39:36 +00:00
}
2012-06-03 10:07:54 +02:00
#define OFFSET(x) offsetof(LibRTMPContext, x)
#define DEC AV_OPT_FLAG_DECODING_PARAM
#define ENC AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options [] = {
2014-04-26 13:32:03 +02:00
{ "rtmp_app" , "Name of application to connect to on the RTMP server" , OFFSET ( app ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC | ENC },
{ "rtmp_buffer" , "Set buffer time in milliseconds. The default is 3000." , OFFSET ( client_buffer_time ), AV_OPT_TYPE_STRING , {. str = "3000" }, 0 , 0 , DEC | ENC },
{ "rtmp_conn" , "Append arbitrary AMF data to the Connect message" , OFFSET ( conn ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC | ENC },
{ "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 },
{ "rtmp_live" , "Specify that the media is a live stream." , OFFSET ( live ), AV_OPT_TYPE_INT , {. i64 = 0 }, INT_MIN , INT_MAX , DEC , "rtmp_live" },
{ "any" , "both" , 0 , AV_OPT_TYPE_CONST , {. i64 = - 2 }, 0 , 0 , DEC , "rtmp_live" },
{ "live" , "live stream" , 0 , AV_OPT_TYPE_CONST , {. i64 = - 1 }, 0 , 0 , DEC , "rtmp_live" },
{ "recorded" , "recorded stream" , 0 , AV_OPT_TYPE_CONST , {. i64 = 0 }, 0 , 0 , DEC , "rtmp_live" },
{ "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 },
{ "rtmp_playpath" , "Stream identifier to play or to publish" , OFFSET ( playpath ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC | ENC },
{ "rtmp_subscribe" , "Name of live stream to subscribe to. Defaults to rtmp_playpath." , OFFSET ( subscribe ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC },
{ "rtmp_swfurl" , "URL of the SWF player. By default no value will be sent" , OFFSET ( swfurl ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC | ENC },
{ "rtmp_swfverify" , "URL to player swf file, compute hash/size automatically. (unimplemented)" , OFFSET ( swfverify ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC },
{ "rtmp_tcurl" , "URL of the target stream. Defaults to proto://host[:port]/app." , OFFSET ( tcurl ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , DEC | ENC },
2015-01-23 17:45:35 +01:00
#if CONFIG_NETWORK
2015-01-19 21:01:00 -06:00
{ "rtmp_buffer_size" , "set buffer size in bytes" , OFFSET ( buffer_size ), AV_OPT_TYPE_INT , {. i64 = - 1 }, - 1 , INT_MAX , DEC | ENC },
2015-01-23 17:45:35 +01:00
#endif
2012-06-03 10:07:54 +02:00
{ NULL },
};
#define RTMP_CLASS(flavor)\
static const AVClass lib ## flavor ## _class = {\
.class_name = "lib" #flavor " protocol",\
.item_name = av_default_item_name,\
.option = options,\
.version = LIBAVUTIL_VERSION_INT,\
};
RTMP_CLASS ( rtmp )
2016-02-19 10:39:29 +01:00
const URLProtocol ff_librtmp_protocol = {
2011-04-08 07:41:47 +02:00
. name = "rtmp" ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. url_read_pause = rtmp_read_pause ,
. url_read_seek = rtmp_read_seek ,
2011-12-01 11:44:21 +02:00
. url_get_file_handle = rtmp_get_file_handle ,
2012-06-03 10:07:54 +02:00
. priv_data_size = sizeof ( LibRTMPContext ),
. priv_data_class = & librtmp_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2010-03-28 13:39:36 +00:00
};
2012-06-03 10:07:54 +02:00
RTMP_CLASS ( rtmpt )
2016-02-19 10:39:29 +01:00
const URLProtocol ff_librtmpt_protocol = {
2011-04-08 07:41:47 +02:00
. name = "rtmpt" ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. url_read_pause = rtmp_read_pause ,
. url_read_seek = rtmp_read_seek ,
2011-12-01 11:44:21 +02:00
. url_get_file_handle = rtmp_get_file_handle ,
2012-06-03 10:07:54 +02:00
. priv_data_size = sizeof ( LibRTMPContext ),
. priv_data_class = & librtmpt_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2010-03-28 13:39:36 +00:00
};
2012-06-03 10:07:54 +02:00
RTMP_CLASS ( rtmpe )
2016-02-19 10:39:29 +01:00
const URLProtocol ff_librtmpe_protocol = {
2011-04-08 07:41:47 +02:00
. name = "rtmpe" ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. url_read_pause = rtmp_read_pause ,
. url_read_seek = rtmp_read_seek ,
2011-12-01 11:44:21 +02:00
. url_get_file_handle = rtmp_get_file_handle ,
2012-06-03 10:07:54 +02:00
. priv_data_size = sizeof ( LibRTMPContext ),
. priv_data_class = & librtmpe_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2010-03-28 13:39:36 +00:00
};
2012-06-03 10:07:54 +02:00
RTMP_CLASS ( rtmpte )
2016-02-19 10:39:29 +01:00
const URLProtocol ff_librtmpte_protocol = {
2011-04-08 07:41:47 +02:00
. name = "rtmpte" ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. url_read_pause = rtmp_read_pause ,
. url_read_seek = rtmp_read_seek ,
2011-12-01 11:44:21 +02:00
. url_get_file_handle = rtmp_get_file_handle ,
2012-06-03 10:07:54 +02:00
. priv_data_size = sizeof ( LibRTMPContext ),
. priv_data_class = & librtmpte_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2010-03-28 13:39:36 +00:00
};
2012-06-03 10:07:54 +02:00
RTMP_CLASS ( rtmps )
2016-02-19 10:39:29 +01:00
const URLProtocol ff_librtmps_protocol = {
2011-04-08 07:41:47 +02:00
. name = "rtmps" ,
. url_open = rtmp_open ,
. url_read = rtmp_read ,
. url_write = rtmp_write ,
. url_close = rtmp_close ,
. url_read_pause = rtmp_read_pause ,
. url_read_seek = rtmp_read_seek ,
2011-12-01 11:44:21 +02:00
. url_get_file_handle = rtmp_get_file_handle ,
2012-06-03 10:07:54 +02:00
. priv_data_size = sizeof ( LibRTMPContext ),
. priv_data_class = & librtmps_class ,
2011-12-30 11:38:05 +02:00
. flags = URL_PROTOCOL_FLAG_NETWORK ,
2010-03-28 13:39:36 +00:00
};