From: Ilkka Ollakka Date: Wed, 5 Mar 2008 07:35:52 +0000 (+0200) Subject: RTMP access patch from Miguel Angel Cabrera . X-Git-Tag: 0.9.0-test0~2299 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=5b2c271150699042f2e3ed0f895e88b2e5b68eaf;p=vlc RTMP access patch from Miguel Angel Cabrera . It came in before git-stuff, so applying it this way is my slowness. --- diff --git a/THANKS b/THANKS index 086422d0cc..fe553d11f7 100644 --- a/THANKS +++ b/THANKS @@ -161,6 +161,7 @@ Meelad Zakaria - Persian localisation Michael Mondragon - ncurses compilation fix Michał Trzebiatowski - Polish translation Mickael Hoerdt - IPv6 SSM multicast patch +Miguel Angel Cabrera - RTMP access patch Mike Schrag - directx device selection Mikko Hirvonen - Firefox-1.5.x development configure patch Michel Lanners - fixed typos and AltiVec detection diff --git a/configure.ac b/configure.ac index 0781db07a5..3c0c4b1f93 100644 --- a/configure.ac +++ b/configure.ac @@ -3307,6 +3307,15 @@ if test "${enable_realrtsp}" = "yes"; then VLC_ADD_PLUGINS([access_realrtsp]) fi +dnl +dnl RTMP plugin +dnl +AC_ARG_ENABLE(rtmp, + [ --enable-rtmp RTMP module (default disabled)]) +if test "${enable_rtmp}" = "yes"; then + VLC_ADD_PLUGINS([access_rtmp]) +fi + dnl dnl MP4 module dnl @@ -6185,6 +6194,7 @@ AC_CONFIG_FILES([ modules/access/mms/Makefile modules/access/cdda/Makefile modules/access/rtsp/Makefile + modules/access/rtmp/Makefile modules/access/v4l2/Makefile modules/access/vcd/Makefile modules/access/vcdx/Makefile diff --git a/modules/access/rtmp/Modules.am b/modules/access/rtmp/Modules.am new file mode 100644 index 0000000000..ee3a4effb6 --- /dev/null +++ b/modules/access/rtmp/Modules.am @@ -0,0 +1,5 @@ +SOURCES_access_rtmp = \ + access.c \ + rtmp_amf_flv.c \ + rtmp_amf_flv.h \ + $(NULL) diff --git a/modules/access/rtmp/access.c b/modules/access/rtmp/access.c new file mode 100644 index 0000000000..b61ed2c775 --- /dev/null +++ b/modules/access/rtmp/access.c @@ -0,0 +1,587 @@ +/***************************************************************************** + * access.c: RTMP input. + ***************************************************************************** + * Copyright (C) URJC - LADyR - Luis Lopez Fernandez + * + * Author: Miguel Angel Cabrera Moya + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include /* DOWN: #include */ +#include +#include + +#include "rtmp_amf_flv.h" + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +#define CACHING_TEXT N_("Caching value in ms") +#define CACHING_LONGTEXT N_( \ + "Caching value for RTMP streams. This " \ + "value should be set in milliseconds." ) + +static int Open ( vlc_object_t * ); +static void Close( vlc_object_t * ); + +vlc_module_begin(); + set_description( _("RTMP input") ); + set_shortname( _("RTMP") ); + set_category( CAT_INPUT ); + set_subcategory( SUBCAT_INPUT_ACCESS ); + + add_integer( "rtmp-caching", DEFAULT_PTS_DELAY / 1000, NULL, CACHING_TEXT, + CACHING_LONGTEXT, VLC_TRUE ); + + set_capability( "access2", 10 ); + set_callbacks( Open, Close ); + add_shortcut( "rtmp" ); +vlc_module_end(); + + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +static int Read( access_t *, uint8_t *, size_t ); /*DOWN: last parameter int */ +static int Seek( access_t *, int64_t ); +static int Control( access_t *, int, va_list ); + +static void ThreadControl( vlc_object_t * ); + +/***************************************************************************** + * Open: open the rtmp connection + *****************************************************************************/ +static int Open( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t *) p_this; + access_sys_t *p_sys; + char *psz, *p; + int length_path, length_media_name; + int i; + + /*DOWN: + p_access->info.i_update = 0; + p_access->info.i_size = 0; + p_access->info.i_pos = 0; + p_access->info.b_eof = VLC_FALSE; + p_access->info.i_title = 0; + p_access->info.i_seekpoint = 0; + p_access->pf_read = Read; + p_access->pf_block = Block; + p_access->pf_control = Control; + p_access->pf_seek = Seek; + do + { + p_access->p_sys = (access_sys_t *) malloc( sizeof( access_sys_t ) ); + if( !p_access->p_sys ) + return VLC_ENOMEM; + } while(0); + p_sys = p_access->p_sys; + memset( p_sys, 0, sizeof( access_sys_t ) ); + */ + STANDARD_READ_ACCESS_INIT + + /* Parse URI - remove spaces */ + p = psz = strdup( p_access->psz_path ); + while( (p = strchr( p, ' ' )) != NULL ) + *p = '+'; + vlc_UrlParse( &p_sys->url, psz, 0 ); + free( psz ); + + if( !p_access->psz_access || + strncmp( p_access->psz_access, "rtmp", 4 )) + { + msg_Warn( p_access, "invalid protocol" ); + goto error; + } + + if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' ) + { + msg_Warn( p_access, "invalid host" ); + goto error; + } + + if( p_sys->url.i_port <= 0 ) + p_sys->url.i_port = 1935; + + if ( p_sys->url.psz_path == NULL ) { + msg_Warn( p_access, "invalid path" ); + goto error; + } + + length_path = strlen( p_sys->url.psz_path ); + length_media_name = strlen( strrchr( p_sys->url.psz_path, '/' ) ) - 1; + + p_sys->psz_application = strndup( p_sys->url.psz_path + 1, length_path - length_media_name - 2 ); + p_sys->psz_media = strdup( p_sys->url.psz_path + ( length_path - length_media_name ) ); + + msg_Dbg( p_access, "rtmp: host='%s' port=%d path='%s'", + p_sys->url.psz_host, p_sys->url.i_port, p_sys->url.psz_path ); + + if( p_sys->url.psz_username && *p_sys->url.psz_username ) + { + msg_Dbg( p_access, " user='%s', pwd='%s'", + p_sys->url.psz_username, p_sys->url.psz_password ); + } + + /* Open connection */ + p_sys->fd = net_ConnectTCP( p_access, p_sys->url.psz_host, p_sys->url.i_port ); + if( p_sys->fd == -1 ) + { + int *p_fd_listen; + + msg_Warn( p_access, "cannot connect to %s:%d", p_sys->url.psz_host, p_sys->url.i_port ); + msg_Dbg( p_access, "switching to passive mode" ); + + p_sys->active = 0; + + p_fd_listen = net_ListenTCP( p_access, p_sys->url.psz_host, p_sys->url.i_port ); + if( p_fd_listen == NULL ) + { + msg_Warn( p_access, "cannot listen to %s port %i", p_sys->url.psz_host, p_sys->url.i_port ); + goto error; + } + + p_sys->fd = net_Accept( p_access, p_fd_listen, -1 ); + + net_ListenClose( p_fd_listen ); + + switch( rtmp_handshake_passive( p_this ) ) + { + case -1: + goto error; + case 0: + break; + default: + msg_Err( p_access, "You should not be here" ); + abort(); + } + + p_sys->p_thread = + vlc_object_create( p_access, sizeof( rtmp_control_thread_t ) ); + if( !p_sys->p_thread ) + { + msg_Err( p_access, "out of memory" ); + goto error; + } + + vlc_object_attach( p_sys->p_thread, p_access ); + p_sys->p_thread->b_die = 0; + p_sys->p_thread->b_error= 0; + p_sys->p_thread->fd = p_sys->fd; + p_sys->p_thread->p_fifo_media = block_FifoNew( p_access ); + p_sys->p_thread->p_empty_blocks = block_FifoNew( p_access ); + p_sys->p_thread->has_audio = 0; + p_sys->p_thread->has_video = 0; + p_sys->p_thread->metadata_received = 0; + p_sys->p_thread->first_media_packet = 1; + p_sys->p_thread->flv_tag_previous_tag_size = 0x00000000; /* FLV_TAG_FIRST_PREVIOUS_TAG_SIZE */ + for(i = 0; i < 64; i++) + { + memset( &p_sys->p_thread->rtmp_headers_recv[i], 0, sizeof( rtmp_packet_t ) ); + p_sys->p_thread->rtmp_headers_send[i].length_header = -1; + p_sys->p_thread->rtmp_headers_send[i].stream_index = -1; + p_sys->p_thread->rtmp_headers_send[i].timestamp = -1; + p_sys->p_thread->rtmp_headers_send[i].timestamp_relative = -1; + p_sys->p_thread->rtmp_headers_send[i].length_encoded = -1; + p_sys->p_thread->rtmp_headers_send[i].length_body = -1; + p_sys->p_thread->rtmp_headers_send[i].content_type = -1; + p_sys->p_thread->rtmp_headers_send[i].src_dst = -1; + p_sys->p_thread->rtmp_headers_send[i].body = NULL; + } + + vlc_cond_init( p_sys->p_thread, &p_sys->p_thread->wait ); + vlc_mutex_init( p_sys->p_thread, &p_sys->p_thread->lock ); + + p_sys->p_thread->result_connect = 1; + p_sys->p_thread->result_play = 1; + p_sys->p_thread->result_publish = 1; + + if( vlc_thread_create( p_sys->p_thread, "rtmp control thread", ThreadControl, + VLC_THREAD_PRIORITY_INPUT, VLC_FALSE ) ) + { + msg_Err( p_access, "cannot spawn rtmp control thread" ); + goto error2; + } + } + else + { + p_sys->active = 1; + + switch( rtmp_handshake_active( p_this ) ) + { + case -1: + goto error; + case 0: + break; + default: + msg_Err( p_access, "You should not be here" ); + abort(); + } + + p_sys->p_thread = + vlc_object_create( p_access, sizeof( rtmp_control_thread_t ) ); + if( !p_sys->p_thread ) + { + msg_Err( p_access, "out of memory" ); + goto error; + } + + vlc_object_attach( p_sys->p_thread, p_access ); + p_sys->p_thread->b_die = 0; + p_sys->p_thread->b_error= 0; + p_sys->p_thread->fd = p_sys->fd; + p_sys->p_thread->p_fifo_media = block_FifoNew( p_access ); + p_sys->p_thread->p_empty_blocks = block_FifoNew( p_access ); + p_sys->p_thread->has_audio = 0; + p_sys->p_thread->has_video = 0; + p_sys->p_thread->metadata_received = 0; + p_sys->p_thread->first_media_packet = 1; + p_sys->p_thread->flv_tag_previous_tag_size = 0x00000000; /* FLV_TAG_FIRST_PREVIOUS_TAG_SIZE */ + for(i = 0; i < 64; i++) + { + memset( &p_sys->p_thread->rtmp_headers_recv[i], 0, sizeof( rtmp_packet_t ) ); + p_sys->p_thread->rtmp_headers_send[i].length_header = -1; + p_sys->p_thread->rtmp_headers_send[i].stream_index = -1; + p_sys->p_thread->rtmp_headers_send[i].timestamp = -1; + p_sys->p_thread->rtmp_headers_send[i].timestamp_relative = -1; + p_sys->p_thread->rtmp_headers_send[i].length_encoded = -1; + p_sys->p_thread->rtmp_headers_send[i].length_body = -1; + p_sys->p_thread->rtmp_headers_send[i].content_type = -1; + p_sys->p_thread->rtmp_headers_send[i].src_dst = -1; + p_sys->p_thread->rtmp_headers_send[i].body = NULL; + } + + vlc_cond_init( p_sys->p_thread, &p_sys->p_thread->wait ); + vlc_mutex_init( p_sys->p_thread, &p_sys->p_thread->lock ); + + p_sys->p_thread->result_connect = 1; + p_sys->p_thread->result_play = 1; + p_sys->p_thread->result_publish = 0; + + if( vlc_thread_create( p_sys->p_thread, "rtmp control thread", ThreadControl, + VLC_THREAD_PRIORITY_INPUT, VLC_FALSE ) ) + { + msg_Err( p_access, "cannot spawn rtmp control thread" ); + goto error2; + } + + switch( rtmp_connect_active( p_this ) ) + { + case -1: + goto error2; + case 0: + break; + default: + msg_Err( p_access, "You should not be here" ); + abort(); + } + } + + /* Set vars for reading from fifo */ + p_access->p_sys->flv_packet = NULL; + p_access->p_sys->read_packet = 1; + + /* Wait until enough data is received for extracting metadata */ + while( block_FifoCount( p_access->p_sys->p_thread->p_fifo_media ) < 10 ) + { + msleep(1000); + continue; + } + + /* Update default_pts to a suitable value for rtmp access */ + var_Create( p_access, "rtmp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); + + return VLC_SUCCESS; + +error2: + +error: + vlc_UrlClean( &p_sys->url ); + net_Close( p_sys-> fd ); + free( p_sys ); + return VLC_EGENERIC; +} + +/***************************************************************************** + * Close: close the rtmp connection + *****************************************************************************/ +static void Close( vlc_object_t * p_this ) +{ + access_t *p_access = (access_t *) p_this; + access_sys_t *p_sys = p_access->p_sys; + int i; + +msg_Warn(p_access, "Close"); + +// p_sys->p_thread->b_die = VLC_TRUE; +vlc_object_kill( p_sys->p_thread ); +block_FifoWake( p_sys->p_thread->p_fifo_media ); +block_FifoWake( p_sys->p_thread->p_empty_blocks ); + for( i = 0; i < 5; i++ ) + { + block_t *p_dummy = block_New( p_access, 256 ); + p_dummy->i_dts = 0; + p_dummy->i_pts = 0; + p_dummy->i_length = 0; + memset( p_dummy->p_buffer, 0, p_dummy->i_buffer ); + block_FifoPut( p_sys->p_thread->p_fifo_media, p_dummy ); + } + for( i = 0; i < 5; i++ ) + { + block_t *p_dummy = block_New( p_access, 256 ); + p_dummy->i_dts = 0; + p_dummy->i_pts = 0; + p_dummy->i_length = 0; + memset( p_dummy->p_buffer, 0, p_dummy->i_buffer ); + block_FifoPut( p_sys->p_thread->p_empty_blocks, p_dummy ); + } + vlc_thread_join( p_sys->p_thread ); + + vlc_cond_destroy( &p_sys->p_thread->wait ); + vlc_mutex_destroy( &p_sys->p_thread->lock ); + + block_FifoRelease( p_sys->p_thread->p_fifo_media ); + block_FifoRelease( p_sys->p_thread->p_empty_blocks ); + + net_Close( p_sys->fd ); + + var_Destroy( p_access, "rtmp-caching" ); + + vlc_object_detach( p_sys->p_thread ); + + vlc_UrlClean( &p_sys->url ); + free( p_sys->psz_application ); + free( p_sys->psz_media ); + free( p_sys ); +} + +/***************************************************************************** + * Read: standard read on a file descriptor. + *****************************************************************************/ +static int Read( access_t *p_access, uint8_t *p_buffer, size_t i_len ) +{ + access_sys_t *p_sys = p_access->p_sys; + int i_len_tmp; + + if( p_sys->fd < 0 ) + { + p_access->info.b_eof = VLC_TRUE; + return 0; + } + + i_len_tmp = 0; + + while( i_len_tmp < i_len ) + { + if( p_sys->read_packet ) + { + if( !p_sys->p_thread->metadata_received ) + { + p_sys->flv_packet = flv_get_metadata( p_access ); + + p_sys->p_thread->metadata_received = 1; + } + else + { + if( p_sys->active && block_FifoCount( p_sys->p_thread->p_fifo_media ) == 0 ) + { + p_access->info.b_eof = VLC_TRUE; + break; + } + + p_sys->flv_packet = block_FifoGet( p_sys->p_thread->p_fifo_media ); + if( p_sys->flv_packet == NULL ) + continue; /* Forced wake-up */ + } + + if( p_sys->p_thread->first_media_packet ) + { + p_sys->flv_packet = flv_insert_header( p_access, p_sys->flv_packet ); + + p_sys->p_thread->first_media_packet = 0; + } + } + if( i_len - i_len_tmp >= p_sys->flv_packet->i_buffer ) + { + p_sys->read_packet = 1; + + memcpy( p_buffer + i_len_tmp, p_sys->flv_packet->p_buffer, p_sys->flv_packet->i_buffer ); + block_FifoPut( p_sys->p_thread->p_empty_blocks, p_sys->flv_packet ); + + i_len_tmp += p_sys->flv_packet->i_buffer; + } + else + { + p_sys->read_packet = 0; + + memcpy( p_buffer + i_len_tmp, p_sys->flv_packet->p_buffer, i_len - i_len_tmp); + p_sys->flv_packet->i_buffer -= i_len - i_len_tmp; + memmove( p_sys->flv_packet->p_buffer, p_sys->flv_packet->p_buffer + i_len - i_len_tmp, p_sys->flv_packet->i_buffer ); + + i_len_tmp += i_len - i_len_tmp; + } + } +/*int i; +for(i = 0; i < i_len_tmp; i += 16) +{ + msg_Warn(p_access,"%.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x", +p_buffer[i], p_buffer[i+1], p_buffer[i+2], p_buffer[i+3], p_buffer[i+4], p_buffer[i+5], p_buffer[i+6], p_buffer[i+7], +p_buffer[i+8], p_buffer[i+9], p_buffer[i+10], p_buffer[i+11], p_buffer[i+12], p_buffer[i+13], p_buffer[i+14], p_buffer[i+15]); +}*/ + if( i_len_tmp > 0 ) { + if( p_sys->p_thread->result_publish ) + { + /* Send publish onStatus event only once */ + p_sys->p_thread->result_publish = 0; + + rtmp_send_publish_start( p_access ); + } + + p_access->info.i_pos += i_len_tmp; + + rtmp_send_bytes_read( p_access, p_access->info.i_pos ); + } + + return i_len_tmp; +} + +/***************************************************************************** + * Seek: seek to a specific location in a file + *****************************************************************************/ +static int Seek( access_t *p_access, int64_t i_pos ) +{ +/*msg_Warn ( p_access, "Seek to %lld", i_pos); + switch( rtmp_seek( p_access, i_pos ) ) + { + case -1: + return VLC_EGENERIC; + case 0: + break; + default: + msg_Err( p_access, "You should not be here" ); + abort(); + } +*/ + return VLC_EGENERIC; +} + +/***************************************************************************** + * Control: + *****************************************************************************/ +static int Control( access_t *p_access, int i_query, va_list args ) +{ + vlc_bool_t *pb_bool; + int *pi_int; + int64_t *pi_64; + + switch( i_query ) + { + /* */ + case ACCESS_CAN_SEEK: + case ACCESS_CAN_FASTSEEK: + pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* ); + *pb_bool = VLC_FALSE; /* TODO */ + break; + + case ACCESS_CAN_PAUSE: + pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* ); + *pb_bool = VLC_FALSE; /* TODO */ + break; + + case ACCESS_CAN_CONTROL_PACE: + pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* ); + *pb_bool = VLC_TRUE; + break; + + /* */ + case ACCESS_GET_MTU: + pi_int = (int*)va_arg( args, int * ); + *pi_int = 0; + break; + + case ACCESS_GET_PTS_DELAY: + pi_64 = (int64_t*)va_arg( args, int64_t * ); + *pi_64 = var_GetInteger( p_access, "rtmp-caching" ) * I64C(1000); + break; + + /* */ + case ACCESS_SET_PAUSE_STATE: + /* Nothing to do */ + break; + + case ACCESS_GET_TITLE_INFO: + case ACCESS_SET_TITLE: + case ACCESS_SET_SEEKPOINT: + case ACCESS_SET_PRIVATE_ID_STATE: + case ACCESS_GET_META: + case ACCESS_GET_CONTENT_TYPE: /* DOWN: comment this line */ + return VLC_EGENERIC; + + default: + msg_Warn( p_access, "unimplemented query in control" ); + return VLC_EGENERIC; + } + + return VLC_SUCCESS; +} + +/***************************************************************************** + * ThreadControl: manage control messages and pipe media to Read + *****************************************************************************/ +static void ThreadControl( vlc_object_t *p_this ) +{ + rtmp_control_thread_t *p_thread = (rtmp_control_thread_t *) p_this; + rtmp_packet_t *rtmp_packet; + + rtmp_init_handler( p_thread->rtmp_handler ); + + while( !p_thread->b_die ) + { + + rtmp_packet = rtmp_read_net_packet( p_thread ); + if( rtmp_packet != NULL ) + { + if( rtmp_packet->content_type < 0x01 /* RTMP_CONTENT_TYPE_CHUNK_SIZE */ + || rtmp_packet->content_type > 0x14 ) /* RTMP_CONTENT_TYPE_INVOKE */ + msg_Warn( p_thread, "unknown content type received" ); + else + p_thread->rtmp_handler[rtmp_packet->content_type]( p_thread, rtmp_packet ); + } + else + { + /* Sometimes server close connection too soon */ + if( p_thread->result_connect ) + { + vlc_mutex_lock( &p_thread->lock ); + vlc_cond_signal( &p_thread->wait ); + vlc_mutex_unlock( &p_thread->lock ); + } + + p_thread->b_die = 1; + } + } +} diff --git a/modules/access/rtmp/rtmp_amf_flv.c b/modules/access/rtmp/rtmp_amf_flv.c new file mode 100644 index 0000000000..b85aa762dc --- /dev/null +++ b/modules/access/rtmp/rtmp_amf_flv.c @@ -0,0 +1,2019 @@ +/***************************************************************************** + * rtmp_amf_flv.c: RTMP, AMF and FLV over RTMP implementation. + ***************************************************************************** + * Copyright (C) URJC - LADyR - Luis Lopez Fernandez + * + * Author: Miguel Angel Cabrera Moya + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * RTMP header: + ******************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include /* DOWN: #include */ +#include +#include + +#include +#include +#include + +#include "rtmp_amf_flv.h" + +/* header length (including itself) */ +const uint8_t RTMP_HEADER_SIZE_MASK = 0xC0; +const uint8_t RTMP_HEADER_SIZE_12 = 0x00; +const uint8_t RTMP_HEADER_SIZE_8 = 0x40; +const uint8_t RTMP_HEADER_SIZE_4 = 0x80; +const uint8_t RTMP_HEADER_SIZE_1 = 0xC0; + +/* streams */ +const uint8_t RTMP_HEADER_STREAM_MAX = 64; +const uint8_t RTMP_HEADER_STREAM_INDEX_MASK = 0x3F; + +/* handshake */ +const uint8_t RTMP_HANDSHAKE = 0x03; +const uint16_t RTMP_HANDSHAKE_BODY_SIZE = 1536; + +/* content types */ +const uint8_t RTMP_CONTENT_TYPE_CHUNK_SIZE = 0x01; +const uint8_t RTMP_CONTENT_TYPE_UNKNOWN_02 = 0x02; +const uint8_t RTMP_CONTENT_TYPE_BYTES_READ = 0x03; +const uint8_t RTMP_CONTENT_TYPE_PING = 0x04; +const uint8_t RTMP_CONTENT_TYPE_SERVER_BW = 0x05; +const uint8_t RTMP_CONTENT_TYPE_CLIENT_BW = 0x06; +const uint8_t RTMP_CONTENT_TYPE_UNKNOWN_07 = 0x07; +const uint8_t RTMP_CONTENT_TYPE_AUDIO_DATA = 0x08; +const uint8_t RTMP_CONTENT_TYPE_VIDEO_DATA = 0x09; +const uint8_t RTMP_CONTENT_TYPE_UNKNOWN_0A_0E = 0x0A; +const uint8_t RTMP_CONTENT_TYPE_FLEX_STREAM = 0x0F; +const uint8_t RTMP_CONTENT_TYPE_FLEX_SHARED_OBJECT = 0x10; +const uint8_t RTMP_CONTENT_TYPE_MESSAGE = 0x11; +const uint8_t RTMP_CONTENT_TYPE_NOTIFY = 0x12; +const uint8_t RTMP_CONTENT_TYPE_SHARED_OBJECT = 0x13; +const uint8_t RTMP_CONTENT_TYPE_INVOKE = 0x14; + +/* shared object datatypes */ +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_CONNECT = 0x01; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_DISCONNECT = 0x02; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_SET_ATTRIBUTE = 0x03; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_UPDATE_DATA = 0x04; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_UPDATE_ATTRIBUTE = 0x05; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_SEND_MESSAGE = 0x06; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_STATUS = 0x07; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_CLEAR_DATA = 0x08; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_DELETE_DATA = 0x09; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_DELETE_ATTRIBUTE = 0x0A; +const uint8_t RTMP_SHARED_OBJECT_DATATYPE_INITIAL_DATA = 0x0B; + +/* pings */ +const uint16_t RTMP_PING_CLEAR_STREAM = 0x0000; +const uint16_t RTMP_PING_CLEAR_PLAYING_BUFFER = 0x0001; +const uint16_t RTMP_PING_BUFFER_TIME_CLIENT = 0x0003; +const uint16_t RTMP_PING_RESET_STREAM = 0x0004; +const uint16_t RTMP_PING_CLIENT_FROM_SERVER = 0x0006; +const uint16_t RTMP_PING_PONG_FROM_CLIENT = 0x0007; + +/* pings sizes */ +const uint8_t RTMP_PING_SIZE_CLEAR_STREAM = 6; +const uint8_t RTMP_PING_SIZE_CLEAR_PLAYING_BUFFER = 6; +const uint8_t RTMP_PING_SIZE_BUFFER_TIME_CLIENT = 10; +/*const uint8_t RTMP_PING_SIZE_RESET_STREAM = 0x0004; TODO +const uint8_t RTMP_PING_SIZE_CLIENT_FROM_SERVER = 0x0006; +const uint8_t RTMP_PING_SIZE_PONG_FROM_CLIENT = 0x0007; +*/ + +/* misc */ +const uint16_t MAX_EMPTY_BLOCKS = 200; /* empty_blocks in fifo for media*/ +const uint16_t RTMP_BODY_SIZE_ALLOC = 1024; +const uint32_t RTMP_TIME_CLIENT_BUFFER = 5000; /* miliseconds */ +const uint8_t RTMP_CONTROL_STREAM_INDEX_DEFAULT = 0x02; +const uint8_t RTMP_AMF_STREAM_INDEX_DEFAULT = 0x03; +const uint32_t RTMP_SRC_DST_CONNECT_OBJECT = 0x00000000; +const uint32_t RTMP_SRC_DST_CONNECT_OBJECT2 = 0x00000001; +const uint32_t RTMP_SRC_DST_DEFAULT = 0x01000000; +const uint64_t RTMP_AUDIOCODECS = 0x4083380000000000; +const uint64_t RTMP_VIDEOCODECS = 0x405f000000000000; +const uint64_t RTMP_VIDEOFUNCTION = 0x3ff0000000000000; + +/***************************************************************************** + * AMF header: + ******************************************************************************/ + +/* packet sizes */ +const uint8_t AMF_PACKET_SIZE_VIDEO = 128; +const uint8_t AMF_PACKET_SIZE_AUDIO = 64; + +/* boolean constants */ +const uint8_t AMF_BOOLEAN_FALSE = 0x00; +const uint8_t AMF_BOOLEAN_TRUE = 0x01; + +/* datatypes */ +const uint8_t AMF_DATATYPE_NUMBER = 0x00; +const uint8_t AMF_DATATYPE_BOOLEAN = 0x01; +const uint8_t AMF_DATATYPE_STRING = 0x02; +const uint8_t AMF_DATATYPE_OBJECT = 0x03; +const uint8_t AMF_DATATYPE_MOVIE_CLIP = 0x04; +const uint8_t AMF_DATATYPE_NULL = 0x05; +const uint8_t AMF_DATATYPE_UNDEFINED = 0x06; +const uint8_t AMF_DATATYPE_REFERENCE = 0x07; +const uint8_t AMF_DATATYPE_MIXED_ARRAY = 0x08; +const uint8_t AMF_DATATYPE_END_OF_OBJECT = 0x09; +const uint8_t AMF_DATATYPE_ARRAY = 0x0A; +const uint8_t AMF_DATATYPE_DATE = 0x0B; +const uint8_t AMF_DATATYPE_LONG_STRING = 0x0C; +const uint8_t AMF_DATATYPE_UNSUPPORTED = 0x0D; +const uint8_t AMF_DATATYPE_RECORDSET = 0x0E; +const uint8_t AMF_DATATYPE_XML = 0x0F; +const uint8_t AMF_DATATYPE_TYPED_OBJECT = 0x10; +const uint8_t AMF_DATATYPE_AMF3_DATA = 0x11; + +/* datatypes sizes */ +const uint8_t AMF_DATATYPE_SIZE_NUMBER = 9; +const uint8_t AMF_DATATYPE_SIZE_BOOLEAN = 2; +const uint8_t AMF_DATATYPE_SIZE_STRING = 3; +const uint8_t AMF_DATATYPE_SIZE_OBJECT = 1; +const uint8_t AMF_DATATYPE_SIZE_NULL = 1; +const uint8_t AMF_DATATYPE_SIZE_OBJECT_VARIABLE = 2; +const uint8_t AMF_DATATYPE_SIZE_MIXED_ARRAY = 5; +const uint8_t AMF_DATATYPE_SIZE_END_OF_OBJECT = 3; + +/* amf remote calls */ +const uint64_t AMF_CALL_NETCONNECTION_CONNECT = 0x3FF0000000000000; +const uint64_t AMF_CALL_NETCONNECTION_CONNECT_AUDIOCODECS = 0x4083380000000000; +const uint64_t AMF_CALL_NETCONNECTION_CONNECT_VIDEOCODECS = 0x405F000000000000; +const uint64_t AMF_CALL_NETCONNECTION_CONNECT_VIDEOFUNCTION = 0x3FF0000000000000; +const uint64_t AMF_CALL_NETCONNECTION_CONNECT_OBJECTENCODING = 0x0; +const double AMF_CALL_STREAM_CLIENT_NUMBER = 3.0; +const uint64_t AMF_CALL_NETSTREAM_PLAY = 0x0; + +/***************************************************************************** + * FLV header: + ******************************************************************************/ +const uint8_t FLV_HEADER_SIGNATURE[3] = { 0x46, 0x4C, 0x56 }; /* always "FLV" */ +const uint8_t FLV_HEADER_VERSION = 0x01; +const uint8_t FLV_HEADER_AUDIO = 0x04; +const uint8_t FLV_HEADER_VIDEO = 0x01; +const uint32_t FLV_HEADER_SIZE = 0x00000009; /* always 9 for known FLV files */ + +const uint32_t FLV_TAG_FIRST_PREVIOUS_TAG_SIZE = 0x00000000; +const uint8_t FLV_TAG_PREVIOUS_TAG_SIZE = 4; +const uint8_t FLV_TAG_SIZE = 11; + +/* audio stereo types */ +const uint8_t FLV_AUDIO_STEREO_MASK = 0x01; +const uint8_t FLV_AUDIO_STEREO_MONO = 0x00; +const uint8_t FLV_AUDIO_STEREO_STEREO = 0x01; + +/* audio size */ +const uint8_t FLV_AUDIO_SIZE_MASK = 0x02; +const uint8_t FLV_AUDIO_SIZE_8_BIT = 0x00; +const uint8_t FLV_AUDIO_SIZE_16_BIT = 0x02; + +/* audio rate */ +const uint8_t FLV_AUDIO_RATE_MASK = 0x0C; +const uint8_t FLV_AUDIO_RATE_5_5_KHZ = 0x00; +const uint8_t FLV_AUDIO_RATE_11_KHZ = 0x04; +const uint8_t FLV_AUDIO_RATE_22_KHZ = 0x08; +const uint8_t FLV_AUDIO_RATE_44_KHZ = 0x0C; + +/* audio codec types */ +const uint8_t FLV_AUDIO_CODEC_ID_MASK = 0xF0; +const uint8_t FLV_AUDIO_CODEC_ID_UNCOMPRESSED = 0x00; +const uint8_t FLV_AUDIO_CODEC_ID_ADPCM = 0x10; +const uint8_t FLV_AUDIO_CODEC_ID_MP3 = 0x20; +const uint8_t FLV_AUDIO_CODEC_ID_NELLYMOSER_8KHZ_MONO = 0x50; +const uint8_t FLV_AUDIO_CODEC_ID_NELLYMOSER = 0x60; + +/* video codec types */ +const uint8_t FLV_VIDEO_CODEC_ID_MASK = 0x0F; +const uint8_t FLV_VIDEO_CODEC_ID_SORENSEN_H263 = 0x02; +const uint8_t FLV_VIDEO_CODEC_ID_SCREEN_VIDEO = 0x03; +const uint8_t FLV_VIDEO_CODEC_ID_ON2_VP6 = 0x04; +const uint8_t FLV_VIDEO_CODEC_ID_ON2_VP6_ALPHA = 0x05; +const uint8_t FLV_VIDEO_CODEC_ID_SCREEN_VIDEO_2 = 0x06; + +/* video frame types */ +const uint8_t FLV_VIDEO_FRAME_TYPE_MASK = 0xF0; +const uint8_t FLV_VIDEO_FRAME_TYPE_KEYFRAME = 0x10; +const uint8_t FLV_VIDEO_FRAME_TYPE_INTER_FRAME = 0x20; +const uint8_t FLV_VIDEO_FRAME_TYPE_DISPOSABLE_INTER_FRAME = 0x30; +/***************************************************************************** + * static RTMP functions: + ******************************************************************************/ +static void rtmp_handler_null ( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); +static void rtmp_handler_invoke ( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); +static void rtmp_handler_audio_data ( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); +static void rtmp_handler_video_data ( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); +static void rtmp_handler_notify ( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); + +static rtmp_packet_t *rtmp_new_packet( rtmp_control_thread_t *p_thread, uint8_t stream_index, uint32_t timestamp, uint8_t content_type, uint32_t src_dst, rtmp_body_t *body ); + +static uint8_t *rtmp_encode_packet( access_t *p_access, rtmp_packet_t *rtmp_packet ); +static rtmp_packet_t *rtmp_encode_NetConnection_Connect_result( rtmp_control_thread_t *p_thread, double number ); +static rtmp_packet_t *rtmp_encode_createStream_result( rtmp_control_thread_t *p_thread, double number ); +static uint8_t rtmp_encode_header_size( access_t *p_access, uint8_t header_size ); +static uint8_t rtmp_decode_header_size( vlc_object_t *p_this, uint8_t header_size ); + +static rtmp_body_t *rtmp_body_new( void ); +static void rtmp_body_append( rtmp_body_t *rtmp_body, uint8_t *buffer, uint8_t length ); + +static uint8_t *ping_encode( uint16_t type, uint32_t src_dst, uint32_t third_arg, uint32_t fourth_arg );/*TODO: change function name*/ + +/***************************************************************************** + * static AMF functions: + ******************************************************************************/ +static uint8_t *amf_encode_element( uint8_t element, const void *value ); +static uint8_t *amf_encode_object_variable( const char *key, uint8_t element, const void *value ); +static double amf_decode_number( uint8_t **buffer ); +static int amf_decode_boolean( uint8_t **buffer ); +static char *amf_decode_string( uint8_t **buffer ); +static char *amf_decode_object( uint8_t **buffer ); + +/***************************************************************************** + * static FLV functions: + ******************************************************************************/ +static block_t *flv_new_packet( rtmp_control_thread_t *p_thread, rtmp_packet_t *rtmp_packet ); +static void flv_rebuild( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); +static void flv_get_metadata_audio( rtmp_packet_t *packet_audio, uint8_t *stereo, uint8_t *audiosamplesize, uint8_t *audiosamplerate, uint8_t *audiocodecid ); +static void flv_get_metadata_video( rtmp_packet_t *packet_video, uint8_t *videocodecid, uint8_t *frametype ); +static rtmp_packet_t *flv_build_onMetaData( access_t *p_access, uint64_t duration, uint8_t stereo, uint8_t audiosamplesize, uint8_t audiosamplerate, uint8_t audiocodecid, uint8_t videocodecid ); + +/***************************************************************************** + * RTMP implementation: + ******************************************************************************/ + +/***************************************************************************** + * rtmp_handshake_passive: + *******************************************************************************/ +int rtmp_handshake_passive( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t *) p_this; + access_sys_t *p_sys = p_access->p_sys; + uint8_t p_read[RTMP_HANDSHAKE_BODY_SIZE + 1]; + uint8_t p_write[RTMP_HANDSHAKE_BODY_SIZE * 2 + 1]; + ssize_t i_ret; + int i; + + /* Receive handshake */ + i_ret = net_Read( p_access, p_sys->fd, NULL, p_read, RTMP_HANDSHAKE_BODY_SIZE + 1, VLC_TRUE ); + if( i_ret != RTMP_HANDSHAKE_BODY_SIZE + 1 ) + { + msg_Err( p_access, "failed to receive handshake" ); + return -1; + } + + /* Check handshake */ + if ( p_read[0] != RTMP_HANDSHAKE ) + { + msg_Err( p_access, "first byte in handshake corrupt" ); + return -1; + } + + /* Answer handshake */ + p_write[0] = RTMP_HANDSHAKE; + memset( p_write + 1, 0, RTMP_HANDSHAKE_BODY_SIZE ); + memcpy( p_write + 1 + RTMP_HANDSHAKE_BODY_SIZE, p_read + 1, RTMP_HANDSHAKE_BODY_SIZE ); + + /* Send handshake*/ + i_ret = net_Write( p_access, p_sys->fd, NULL, p_write, RTMP_HANDSHAKE_BODY_SIZE * 2 + 1 ); + if( i_ret != RTMP_HANDSHAKE_BODY_SIZE * 2 + 1 ) + { + msg_Err( p_access, "failed to send handshake" ); + return -1; + } + + /* Receive acknowledge */ + i_ret = net_Read( p_access, p_sys->fd, NULL, p_read, RTMP_HANDSHAKE_BODY_SIZE, VLC_TRUE ); + if( i_ret != RTMP_HANDSHAKE_BODY_SIZE ) + { + msg_Err( p_access, "failed to receive acknowledge" ); + return -1; + } + + /* Check acknowledge */ + for(i = 8; i < RTMP_HANDSHAKE_BODY_SIZE; i++ ) + if( p_write[i + 1] != p_read[i] ) + { + msg_Err( p_access, "body acknowledge received corrupt" ); + return -1; + } + + return 0; +} +/***************************************************************************** + * rtmp_handshake_active: + *******************************************************************************/ +int rtmp_handshake_active( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t *) p_this; + access_sys_t *p_sys = p_access->p_sys; + uint8_t p_read[RTMP_HANDSHAKE_BODY_SIZE * 2 + 1]; + uint8_t p_write[RTMP_HANDSHAKE_BODY_SIZE + 1]; + ssize_t i_ret; + int i; + + p_write[0] = RTMP_HANDSHAKE; + for( i = 0; i < RTMP_HANDSHAKE_BODY_SIZE; i++ ) + p_write[i + 1] = i & 0xFF; + + /* Send handshake*/ + i_ret = net_Write( p_access, p_sys->fd, NULL, p_write, RTMP_HANDSHAKE_BODY_SIZE + 1 ); + if( i_ret != RTMP_HANDSHAKE_BODY_SIZE + 1 ) + { + msg_Err( p_access, "failed to send handshake" ); + return -1; + } + + /* Receive handshake */ + i_ret = net_Read( p_access, p_sys->fd, NULL, p_read, RTMP_HANDSHAKE_BODY_SIZE * 2 + 1, VLC_TRUE ); + if( i_ret != RTMP_HANDSHAKE_BODY_SIZE * 2 + 1 ) + { + msg_Err( p_access, "failed to receive handshake" ); + return -1; + } + + /* Check handshake */ + if( p_read[0] != RTMP_HANDSHAKE ) + { + msg_Err( p_access, "first byte in handshake received corrupt" ); + return -1; + } + + for(i = 0; i < RTMP_HANDSHAKE_BODY_SIZE; i++ ) + if( p_write[i + 1] != p_read[i + 1 + RTMP_HANDSHAKE_BODY_SIZE] ) + { + msg_Err( p_access, "body handshake received corrupt" ); + return -1; + } + + /* Acknowledge handshake */ + i_ret = net_Write( p_access, p_sys->fd, NULL, p_read + 1, RTMP_HANDSHAKE_BODY_SIZE ); + if( i_ret != RTMP_HANDSHAKE_BODY_SIZE ) + { + msg_Err( p_access, "failed to acknowledge handshake" ); + return -1; + } + + return 0; +} + +/***************************************************************************** + * rtmp_connect_active: + ******************************************************************************/ +int rtmp_connect_active( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t *) p_this; + access_sys_t *p_sys = p_access->p_sys; + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + char *tmp_url; + ssize_t i_ret; + + /* Build NetConnection.connect call */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "connect" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "connect" ) ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, + &AMF_CALL_NETCONNECTION_CONNECT ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_OBJECT ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "app", + AMF_DATATYPE_STRING, p_sys->psz_application ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "app" ) + + AMF_DATATYPE_SIZE_STRING + strlen( p_sys->psz_application ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "flashVer", + AMF_DATATYPE_STRING, "LNX 9,0,48,0" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "flashVer" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "LNX 9,0,48,0" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "swfUrl", + AMF_DATATYPE_STRING, "file:///mac.flv" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "swfUrl" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "file:///mac.flv" ) ); + free ( tmp_buffer ); + + tmp_url = (char *) malloc( strlen( "rtmp://") + strlen( p_sys->url.psz_buffer ) + 1 ); + sprintf( tmp_url, "rtmp://%s", p_sys->url.psz_buffer ); + tmp_buffer = amf_encode_object_variable( "tcUrl", + AMF_DATATYPE_STRING, tmp_url ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "tcUrl" ) + + AMF_DATATYPE_SIZE_STRING + strlen( tmp_url ) ); + free( tmp_url ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "fpad", + AMF_DATATYPE_BOOLEAN, &AMF_BOOLEAN_FALSE ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "fpad" ) + + AMF_DATATYPE_SIZE_BOOLEAN ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "audioCodecs", + AMF_DATATYPE_NUMBER, &AMF_CALL_NETCONNECTION_CONNECT_AUDIOCODECS ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "audioCodecs" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "videoCodecs", + AMF_DATATYPE_NUMBER, &AMF_CALL_NETCONNECTION_CONNECT_VIDEOCODECS ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "videoCodecs" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "videoFunction", + AMF_DATATYPE_NUMBER, &AMF_CALL_NETCONNECTION_CONNECT_VIDEOFUNCTION ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "videoFunction" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "pageUrl", + AMF_DATATYPE_STRING, "file:///mac.html" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "pageUrl" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "file:///mac.html" ) ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "objectEncoding", + AMF_DATATYPE_NUMBER, &AMF_CALL_NETCONNECTION_CONNECT_OBJECTENCODING ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "objectEncoding" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element ( AMF_DATATYPE_END_OF_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_END_OF_OBJECT ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_INVOKE, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Call NetConnection.connect */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send call NetConnection.connect" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + /* Wait for NetConnection.connect result */ + vlc_mutex_lock( &p_sys->p_thread->lock ); + vlc_cond_wait( &p_sys->p_thread->wait, &p_sys->p_thread->lock ); + vlc_mutex_unlock( &p_sys->p_thread->lock ); + + if( p_sys->p_thread->result_connect ) + { + msg_Err( p_access, "failed call NetConnection.connect" ); + return -1; + } + + /* Force control thread to stop if receive NetStream.play call and wait is not ready */ + vlc_mutex_lock( &p_sys->p_thread->lock ); + + /* Build NetStream.createStream call */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "createStream" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "createStream" ) ); + free( tmp_buffer ); + + p_sys->p_thread->stream_client = AMF_CALL_STREAM_CLIENT_NUMBER; + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, + &AMF_CALL_STREAM_CLIENT_NUMBER ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NULL, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NULL ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_INVOKE, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Call NetStream.createStream */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send call NetStream.createStream" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); +/*TODO: read server stream number*/ + /* Build ping packet */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = ping_encode( RTMP_PING_BUFFER_TIME_CLIENT, RTMP_SRC_DST_CONNECT_OBJECT, RTMP_TIME_CLIENT_BUFFER, 0 ); + rtmp_body_append( rtmp_body, tmp_buffer, RTMP_PING_SIZE_BUFFER_TIME_CLIENT ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_CONTROL_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_PING, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Send ping packet */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send ping BUFFER_TIME_CLIENT" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + /* Build NetStream.play call */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "play" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "play" ) ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, + &AMF_CALL_NETSTREAM_PLAY ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NULL, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NULL ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, p_sys->psz_media ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( p_sys->psz_media ) ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_INVOKE, RTMP_SRC_DST_DEFAULT, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Call NetStream.play */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send call NetStream.play" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + /* Build ping packet */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = ping_encode( RTMP_PING_BUFFER_TIME_CLIENT, RTMP_SRC_DST_CONNECT_OBJECT2, RTMP_TIME_CLIENT_BUFFER, 0 ); + rtmp_body_append( rtmp_body, tmp_buffer, RTMP_PING_SIZE_BUFFER_TIME_CLIENT ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_CONTROL_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_PING, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Send ping packet */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send ping BUFFER_TIME_CLIENT" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + /* Wait for NetStream.play.start result */ + vlc_cond_wait( &p_sys->p_thread->wait, &p_sys->p_thread->lock ); + vlc_mutex_unlock( &p_sys->p_thread->lock ); + + if( p_sys->p_thread->result_play ) + { + msg_Err( p_access, "failed call NetStream.play" ); + return -1; + } + + /* Next packet is the beginning of flv stream */ + msg_Dbg(p_access, "Next packet is the beginning of flv stream"); + + return 0; +} +/* TODO +int +rtmp_seek( access_t *p_access, int64_t i_pos ) +{ + access_sys_t *p_sys = p_access->p_sys; + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + uint64_t tmp_number; + ssize_t i_ret; +msg_Warn(p_access, "i_pos %lld", i_pos); + // Build NetStream.seek call // + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "seek" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "seek" ) ); + free( tmp_buffer ); + + tmp_number = 0; + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, + &tmp_number ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NULL, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NULL ); + free( tmp_buffer ); +//TODO: convert i_pos to double and see if they are milliseconds + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, + &i_pos ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_DATATYPE_INVOKE, RTMP_SRC_DST_DEFAULT, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + // Call NetStream.seek // + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret < rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed call NetStream.seek" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + // Receive TODO: see what // + rtmp_packet = rtmp_read_net_packet( p_access, p_sys->fd ); + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + + return 0; +} +*/ +int +rtmp_send_bytes_read( access_t *p_access, uint32_t reply ) +{ + access_sys_t *p_sys = p_access->p_sys; + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + ssize_t i_ret; + + /* Build bytes read packet */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = (uint8_t *) malloc( sizeof( uint32_t ) * sizeof( uint8_t ) ); + reply = hton32( reply ); + memcpy( tmp_buffer, &reply, sizeof( uint32_t ) ); + + rtmp_body_append( rtmp_body, tmp_buffer, sizeof( uint32_t ) ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_CONTROL_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_BYTES_READ, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Send bytes read packet */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send bytes read" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + return 0; +} + +int +rtmp_send_publish_start( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + ssize_t i_ret; + + /* Build publish start event */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "onStatus" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "onStatus" ) ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, + &p_sys->p_thread->stream_server ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NULL, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NULL ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_OBJECT ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "level", + AMF_DATATYPE_STRING, "status" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "level" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "status" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "code", + AMF_DATATYPE_STRING, "NetStream.Publish.Start" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "code" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "NetStream.Publish.Start" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "description", + AMF_DATATYPE_STRING, "" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "description" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "details", + AMF_DATATYPE_STRING, p_sys->p_thread->psz_publish ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "details" ) + + AMF_DATATYPE_SIZE_STRING + strlen( p_sys->p_thread->psz_publish ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "clientid", + AMF_DATATYPE_NUMBER, &p_sys->p_thread->stream_client ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "videoCodecs" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element ( AMF_DATATYPE_END_OF_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_END_OF_OBJECT ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_sys->p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_INVOKE, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + tmp_buffer = rtmp_encode_packet( p_access, rtmp_packet ); + + /* Send publish start event */ + i_ret = net_Write( p_access, p_sys->fd, NULL, tmp_buffer, rtmp_packet->length_encoded ); + if( i_ret != rtmp_packet->length_encoded ) + { + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + msg_Err( p_access, "failed send publish start event" ); + return -1; + } + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); + free( tmp_buffer ); + + return 0; +} + +rtmp_packet_t * +rtmp_read_net_packet( rtmp_control_thread_t *p_thread ) +{ + int interchunk_headers; + uint8_t p_read[12], trash; + rtmp_packet_t *rtmp_packet; + ssize_t i_ret; + int i; + + i_ret = net_Read( p_thread, p_thread->fd, NULL, p_read, 1, VLC_TRUE ); + if( i_ret != 1 ) + { + msg_Err( p_thread, "rtmp_read_net_packet: net_Read error"); + return NULL; + } + + rtmp_packet = (rtmp_packet_t *) malloc( sizeof( rtmp_packet_t ) ); + rtmp_packet->body = (rtmp_body_t *) malloc( sizeof( rtmp_body_t ) ); + + rtmp_packet->length_header = rtmp_decode_header_size( (vlc_object_t *) p_thread, p_read[0] & RTMP_HEADER_SIZE_MASK ); + rtmp_packet->stream_index = p_read[0] & RTMP_HEADER_STREAM_INDEX_MASK; + + i_ret = net_Read( p_thread, p_thread->fd, NULL, p_read + 1, rtmp_packet->length_header - 1, VLC_TRUE ); + if( i_ret != rtmp_packet->length_header - 1 ) + goto error; + + if( rtmp_packet->length_header >= 4 ) + { + p_read[0] = 0; + + rtmp_packet->timestamp_relative = ntoh32( *(uint32_t *) p_read ); + rtmp_packet->timestamp = p_thread->rtmp_headers_recv[rtmp_packet->stream_index].timestamp + rtmp_packet->timestamp_relative; + + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].timestamp = rtmp_packet->timestamp; + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].timestamp_relative = rtmp_packet->timestamp_relative; + } + else + { + rtmp_packet->timestamp_relative = p_thread->rtmp_headers_recv[rtmp_packet->stream_index].timestamp_relative; + rtmp_packet->timestamp = p_thread->rtmp_headers_recv[rtmp_packet->stream_index].timestamp + rtmp_packet->timestamp_relative; + + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].timestamp = rtmp_packet->timestamp; + } + + if( rtmp_packet->length_header >= 8 ) + { + p_read[3] = 0; + rtmp_packet->body->length_body = ntoh32( *(uint32_t *) (p_read + 3) ); + rtmp_packet->content_type = p_read[7]; + + interchunk_headers = rtmp_packet->body->length_body / AMF_PACKET_SIZE_VIDEO; + rtmp_packet->length_encoded = rtmp_packet->length_header + rtmp_packet->body->length_body + interchunk_headers; + + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].length_header = rtmp_packet->length_header; + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].content_type = rtmp_packet->content_type; + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].length_encoded = rtmp_packet->length_encoded; + } + else + { + rtmp_packet->content_type = p_thread->rtmp_headers_recv[rtmp_packet->stream_index].content_type; + + rtmp_packet->body->length_body = + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].length_encoded - + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].length_header - + (p_thread->rtmp_headers_recv[rtmp_packet->stream_index].length_encoded - + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].length_header) / AMF_PACKET_SIZE_VIDEO; + + interchunk_headers = rtmp_packet->body->length_body / AMF_PACKET_SIZE_VIDEO; + rtmp_packet->length_encoded = rtmp_packet->length_header + rtmp_packet->body->length_body + interchunk_headers; + } + + if( rtmp_packet->length_header >= 12 ) + { + rtmp_packet->src_dst = ntoh32( *(uint32_t *) (p_read + 8) ); + + p_thread->rtmp_headers_recv[rtmp_packet->stream_index].src_dst = rtmp_packet->src_dst; + } + else + { + rtmp_packet->src_dst = p_thread->rtmp_headers_recv[rtmp_packet->stream_index].src_dst; + } + + rtmp_packet->body->length_buffer = rtmp_packet->body->length_body; + rtmp_packet->body->body = (uint8_t *) malloc( rtmp_packet->body->length_buffer * sizeof( uint8_t ) ); + + if( rtmp_packet->body->length_body % AMF_PACKET_SIZE_VIDEO == 0 ) + interchunk_headers--; + + for(i = 0; i < interchunk_headers; i++) + { + i_ret = net_Read( p_thread, p_thread->fd, NULL, + rtmp_packet->body->body + (i * AMF_PACKET_SIZE_VIDEO), AMF_PACKET_SIZE_VIDEO, VLC_TRUE ); + if( i_ret != AMF_PACKET_SIZE_VIDEO ) + goto error2; + i_ret = net_Read( p_thread, p_thread->fd, NULL, &trash, 1, VLC_TRUE ); + if( i_ret != 1 ) + goto error2; + } + + i_ret = net_Read( p_thread, p_thread->fd, NULL, + rtmp_packet->body->body + (i * AMF_PACKET_SIZE_VIDEO), + rtmp_packet->body->length_body - (i * AMF_PACKET_SIZE_VIDEO), VLC_TRUE ); + if( i_ret != rtmp_packet->body->length_body - (i * AMF_PACKET_SIZE_VIDEO) ) + goto error2; + + return rtmp_packet; + +error2: + free( rtmp_packet->body->body ); +error: + free( rtmp_packet->body ); + free( rtmp_packet ); + + msg_Err( p_thread, "rtmp_read_net_packet: net_Read error"); + + return NULL; +} + +void +rtmp_init_handler( rtmp_handler_t *rtmp_handler ) +{ + rtmp_handler[RTMP_CONTENT_TYPE_CHUNK_SIZE] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_UNKNOWN_02] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_BYTES_READ] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_PING] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_SERVER_BW] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_CLIENT_BW] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_UNKNOWN_07] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_AUDIO_DATA] = rtmp_handler_audio_data; + rtmp_handler[RTMP_CONTENT_TYPE_VIDEO_DATA] = rtmp_handler_video_data; + rtmp_handler[RTMP_CONTENT_TYPE_UNKNOWN_0A_0E] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_FLEX_STREAM] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_FLEX_SHARED_OBJECT] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_MESSAGE] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_NOTIFY] = rtmp_handler_notify; + rtmp_handler[RTMP_CONTENT_TYPE_SHARED_OBJECT] = rtmp_handler_null; + rtmp_handler[RTMP_CONTENT_TYPE_INVOKE] = rtmp_handler_invoke; +} + +static void +rtmp_handler_null( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ) +{ + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); +} + +static void +rtmp_handler_audio_data( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ) +{ + block_t *p_buffer; + + if( !rtmp_control_thread->has_audio ) + { + rtmp_control_thread->has_audio = 1; + + flv_get_metadata_audio( rtmp_packet, + &rtmp_control_thread->metadata_stereo, &rtmp_control_thread->metadata_samplesize, + &rtmp_control_thread->metadata_samplerate, &rtmp_control_thread->metadata_audiocodecid ); + } + + flv_rebuild( rtmp_control_thread, rtmp_packet ); + p_buffer = flv_new_packet( rtmp_control_thread, rtmp_packet ); + block_FifoPut( rtmp_control_thread->p_fifo_media, p_buffer ); + + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); +} + +static void +rtmp_handler_video_data( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ) +{ + block_t *p_buffer; + + if( !rtmp_control_thread->has_video ) + { + rtmp_control_thread->has_video = 1; + + flv_get_metadata_video( rtmp_packet, + &rtmp_control_thread->metadata_videocodecid, &rtmp_control_thread->metadata_frametype ); + } + flv_rebuild( rtmp_control_thread, rtmp_packet ); + p_buffer = flv_new_packet( rtmp_control_thread, rtmp_packet ); + block_FifoPut( rtmp_control_thread->p_fifo_media, p_buffer ); + + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); +} + +static void +rtmp_handler_notify( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ) +{ + block_t *p_buffer; + + rtmp_control_thread->metadata_received = 1; + + flv_rebuild( rtmp_control_thread, rtmp_packet ); + p_buffer = flv_new_packet( rtmp_control_thread, rtmp_packet ); + block_FifoPut( rtmp_control_thread->p_fifo_media, p_buffer ); + + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); +} + +static void +rtmp_handler_invoke( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ) +{ + access_t *p_access = (access_t *) rtmp_control_thread; + rtmp_packet_t *tmp_rtmp_packet; + uint8_t *i, *end, *tmp_buffer; + double number; + char *string, *string2; + ssize_t i_ret; + + i = rtmp_packet->body->body; + end = rtmp_packet->body->body + rtmp_packet->body->length_body; + + i++; /* Pass over AMF_DATATYPE_STRING */ + string = amf_decode_string( &i ); + + i++; /* Pass over AMF_DATATYPE_NUMBER */ + number = amf_decode_number( &i ); + + msg_Dbg( rtmp_control_thread, "%s %.1f", string, number ); + + if( strcmp( "connect", string ) == 0 ) + { + tmp_rtmp_packet = rtmp_encode_NetConnection_Connect_result( rtmp_control_thread, number ); + + tmp_buffer = rtmp_encode_packet( p_access, tmp_rtmp_packet ); + + /* Reply NetConnection.connect */ + i_ret = net_Write( rtmp_control_thread, rtmp_control_thread->fd, NULL, tmp_buffer, tmp_rtmp_packet->length_encoded ); + if( i_ret != tmp_rtmp_packet->length_encoded ) + { + free( tmp_rtmp_packet->body->body ); + free( tmp_rtmp_packet->body ); + free( tmp_rtmp_packet ); + free( tmp_buffer ); + msg_Err( rtmp_control_thread, "failed send reply NetConnection.connect" ); + } + free( tmp_rtmp_packet->body->body ); + free( tmp_rtmp_packet->body ); + free( tmp_rtmp_packet ); + free( tmp_buffer ); + } + else if( strcmp( "createStream", string ) == 0 ) + { + rtmp_control_thread->stream_client = number; + rtmp_control_thread->stream_server = number; + + tmp_rtmp_packet = rtmp_encode_createStream_result( rtmp_control_thread, number ); + + tmp_buffer = rtmp_encode_packet( p_access, tmp_rtmp_packet ); + + /* Reply createStream */ + i_ret = net_Write( rtmp_control_thread, rtmp_control_thread->fd, NULL, tmp_buffer, tmp_rtmp_packet->length_encoded ); + if( i_ret != tmp_rtmp_packet->length_encoded ) + { + free( tmp_rtmp_packet->body->body ); + free( tmp_rtmp_packet->body ); + free( tmp_rtmp_packet ); + free( tmp_buffer ); + msg_Err( rtmp_control_thread, "failed send reply createStream" ); + } + free( tmp_rtmp_packet->body->body ); + free( tmp_rtmp_packet->body ); + free( tmp_rtmp_packet ); + free( tmp_buffer ); + } + else if( strcmp( "publish", string ) == 0 ) + { + i++; + msg_Dbg( rtmp_control_thread, "Null" ); + + i++; + string2 = amf_decode_string( &i ); + msg_Dbg( rtmp_control_thread, "String: %s", string2 ); + + rtmp_control_thread->psz_publish = strdup( string2 ); + + free( string2 ); + } + + free( string ); + + while( i < end ) + { + if( *i == AMF_DATATYPE_NUMBER ) + { + i++; + msg_Dbg( rtmp_control_thread, "Number: %le", amf_decode_number( &i ) ); + } + else if( *i == AMF_DATATYPE_BOOLEAN ) + { + i++; + msg_Dbg( rtmp_control_thread, "Boolean: %s", amf_decode_boolean( &i ) ? "True" : "False" ); + } + else if( *i == AMF_DATATYPE_STRING ) + { + i++; + string = amf_decode_string( &i ); + msg_Dbg( rtmp_control_thread, "String: %s", string ); + free( string ); + } + else if( *i == AMF_DATATYPE_OBJECT ) + { + i++; + msg_Dbg( rtmp_control_thread, "Object" ); + while( ( string = amf_decode_object( &i ) ) != NULL ) + { + if( *i == AMF_DATATYPE_NUMBER ) + { + i++; + msg_Dbg( rtmp_control_thread, "Key: %s Value: %le", string, amf_decode_number( &i ) ); + } + else if( *i == AMF_DATATYPE_BOOLEAN ) + { + i++; + msg_Dbg( rtmp_control_thread, "Key: %s Value: %s", string, amf_decode_boolean( &i ) ? "True" : "False" ); + } + else if( *i == AMF_DATATYPE_STRING ) + { + i++; + string2 = amf_decode_string( &i ); + msg_Dbg( rtmp_control_thread, "Key: %s Value: %s", string, string2 ); + if( strcmp( "code", string ) == 0 ) + { + if( strcmp( "NetConnection.Connect.Success", string2 ) == 0 ) + { + rtmp_control_thread->result_connect = 0; + + vlc_mutex_lock( &rtmp_control_thread->lock ); + vlc_cond_signal( &rtmp_control_thread->wait ); + vlc_mutex_unlock( &rtmp_control_thread->lock ); + } + else if( strcmp( "NetConnection.Connect.InvalidApp", string2 ) == 0 ) + { + rtmp_control_thread->b_die = 1; + + vlc_mutex_lock( &rtmp_control_thread->lock ); + vlc_cond_signal( &rtmp_control_thread->wait ); + vlc_mutex_unlock( &rtmp_control_thread->lock ); + } + else if( strcmp( "NetStream.Play.Start", string2 ) == 0 ) + { + rtmp_control_thread->result_play = 0; + + vlc_mutex_lock( &rtmp_control_thread->lock ); + vlc_cond_signal( &rtmp_control_thread->wait ); + vlc_mutex_unlock( &rtmp_control_thread->lock ); + } + } + free( string2 ); + } + else if( *i == AMF_DATATYPE_NULL) + { + i++; + msg_Dbg( rtmp_control_thread, "Key: %s Value: Null", string ); + } + free( string ); + } + msg_Dbg( rtmp_control_thread, "End of object" ); + } + else if( *i == AMF_DATATYPE_NULL) + { + i++; + msg_Dbg( rtmp_control_thread, "Null" ); + } + } + + free( rtmp_packet->body->body ); + free( rtmp_packet->body ); + free( rtmp_packet ); +} + +/* length header calculated automatically based on last packet in the same channel */ +static rtmp_packet_t * +rtmp_new_packet( rtmp_control_thread_t *p_thread, uint8_t stream_index, uint32_t timestamp, uint8_t content_type, uint32_t src_dst, rtmp_body_t *body ) +{ + int interchunk_headers; + rtmp_packet_t *rtmp_packet; + + rtmp_packet = (rtmp_packet_t *) malloc( sizeof( rtmp_packet_t ) ); + interchunk_headers = body->length_body / AMF_PACKET_SIZE_VIDEO; + + if( src_dst != p_thread->rtmp_headers_send[stream_index].src_dst ) + { + p_thread->rtmp_headers_send[stream_index].timestamp = timestamp; + p_thread->rtmp_headers_send[stream_index].length_body = body->length_body; + p_thread->rtmp_headers_send[stream_index].content_type = content_type; + p_thread->rtmp_headers_send[stream_index].src_dst = src_dst; + + rtmp_packet->length_header = 12; + } + else if( content_type != p_thread->rtmp_headers_send[stream_index].content_type + || body->length_body != p_thread->rtmp_headers_send[stream_index].length_body ) + { + p_thread->rtmp_headers_send[stream_index].timestamp_relative = timestamp; + p_thread->rtmp_headers_send[stream_index].length_body = body->length_body; + p_thread->rtmp_headers_send[stream_index].content_type = content_type; + + rtmp_packet->length_header = 8; + } + else if( timestamp != p_thread->rtmp_headers_send[stream_index].timestamp_relative ) + { + p_thread->rtmp_headers_send[stream_index].timestamp_relative = timestamp; + + rtmp_packet->length_header = 4; + } + else + { + rtmp_packet->length_header = 1; + } + + rtmp_packet->stream_index = stream_index; + if( rtmp_packet->length_header == 12 ) + { + rtmp_packet->timestamp = timestamp; + rtmp_packet->timestamp_relative = 0; + } + else + { + rtmp_packet->timestamp = p_thread->rtmp_headers_send[stream_index].timestamp + timestamp; + rtmp_packet->timestamp_relative = timestamp; + } + rtmp_packet->length_encoded = rtmp_packet->length_header + body->length_body + interchunk_headers; + rtmp_packet->length_body = body->length_body; + rtmp_packet->content_type = content_type; + rtmp_packet->src_dst = src_dst; + + rtmp_packet->body = (rtmp_body_t *) malloc( sizeof( rtmp_body_t ) ); + + rtmp_packet->body->length_body = body->length_body; + rtmp_packet->body->length_buffer = body->length_body; + rtmp_packet->body->body = (uint8_t *) malloc( rtmp_packet->body->length_buffer * sizeof( uint8_t ) ); + memcpy( rtmp_packet->body->body, body->body, rtmp_packet->body->length_body ); + + return rtmp_packet; +} + +/* call sequence for each packet rtmp_new_packet -> rtmp_encode_packet -> send */ +/* no parallelism allowed because of optimization in header length */ +static uint8_t * +rtmp_encode_packet( access_t *p_access, rtmp_packet_t *rtmp_packet ) +{ + uint8_t *out; + int interchunk_headers; + uint32_t length_body, src_dst; + int i, j; + + out = (uint8_t *) malloc( rtmp_packet->length_encoded * sizeof( uint8_t ) ); + interchunk_headers = rtmp_packet->body->length_body / AMF_PACKET_SIZE_VIDEO; + + out[0] = rtmp_encode_header_size( p_access, rtmp_packet->length_header ) + rtmp_packet->stream_index; + + if( rtmp_packet->length_header >= 4 ) + { /* Timestamp */ + out[1] = 0; + out[2] = 0; + out[3] = 0; + } + if( rtmp_packet->length_header >= 8 ) + { + /* Length without inter chunk headers */ + length_body = hton32( rtmp_packet->body->length_body ); + memcpy( out + 3, &length_body, sizeof( uint32_t ) ); + out[3] = 0; + + out[7] = rtmp_packet->content_type; + } + if( rtmp_packet->length_header >= 12 ) + { + src_dst = hton32( rtmp_packet->src_dst ); + memcpy( out + 8, &src_dst, sizeof( uint32_t ) ); + } + + // Insert inter chunk headers + for(i = 0, j = 0; i < rtmp_packet->body->length_body + interchunk_headers; i++, j++) + { + if( j % AMF_PACKET_SIZE_VIDEO == 0 && j != 0 ) + out[rtmp_packet->length_header + i++] = RTMP_HEADER_SIZE_1 + rtmp_packet->stream_index; + out[rtmp_packet->length_header + i] = rtmp_packet->body->body[j]; + } + + return out; +} + +static rtmp_packet_t * +rtmp_encode_NetConnection_Connect_result( rtmp_control_thread_t *p_thread, double number ) +{ + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + + /* Build NetConnection.connect result */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "_result" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "_result" ) ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NULL, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NULL ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_OBJECT ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "level", + AMF_DATATYPE_STRING, "status" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "level" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "status" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "code", + AMF_DATATYPE_STRING, "NetConnection.Connect.Success" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "code" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "NetConnection.Connect.Success" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "description", + AMF_DATATYPE_STRING, "Connection succeeded." ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "description" ) + + AMF_DATATYPE_SIZE_STRING + strlen( "Connection succeeded" ) ); + free ( tmp_buffer ); + + tmp_buffer = amf_encode_element ( AMF_DATATYPE_END_OF_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_END_OF_OBJECT ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_INVOKE, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + return rtmp_packet; +} + +static rtmp_packet_t * +rtmp_encode_createStream_result( rtmp_control_thread_t *p_thread, double number ) +{ + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + + /* Build createStream result */ + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "_result" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "_result" ) ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NULL, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NULL ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_INVOKE, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + return rtmp_packet; +} + +static uint8_t +rtmp_encode_header_size( access_t *p_access, uint8_t header_size ) +{ + if( header_size == 1 ) + return RTMP_HEADER_SIZE_1; + else if( header_size == 4 ) + return RTMP_HEADER_SIZE_4; + else if( header_size == 8 ) + return RTMP_HEADER_SIZE_8; + else if( header_size == 12 ) + return RTMP_HEADER_SIZE_12; + else + { + msg_Err( p_access, "invalid header size for encoding" ); + return 0; + } +} + +static uint8_t +rtmp_decode_header_size( vlc_object_t *p_this, uint8_t header_size ) +{ + if( header_size == RTMP_HEADER_SIZE_1 ) + return 1; + else if( header_size == RTMP_HEADER_SIZE_4 ) + return 4; + else if( header_size == RTMP_HEADER_SIZE_8 ) + return 8; + else if( header_size == RTMP_HEADER_SIZE_12 ) + return 12; + else + { + msg_Err( p_this, "invalid RTMP_HEADER_SIZE_XX " ); + return 0; + } +} + +/***************************************************************************** + * Body handling implementation: + ******************************************************************************/ +static rtmp_body_t * +rtmp_body_new( void ) +{ + rtmp_body_t *rtmp_body; + + rtmp_body = (rtmp_body_t *) malloc( sizeof( rtmp_body_t ) ); + + rtmp_body->length_body = 0; + rtmp_body->length_buffer = RTMP_BODY_SIZE_ALLOC; + rtmp_body->body = (uint8_t *) malloc( RTMP_BODY_SIZE_ALLOC * sizeof( uint8_t ) ); + + return rtmp_body; +} + +static void +rtmp_body_append( rtmp_body_t *rtmp_body, uint8_t *buffer, uint8_t length ) +{ + if( rtmp_body->length_body + length > rtmp_body->length_buffer ) + { + rtmp_body->length_buffer += RTMP_BODY_SIZE_ALLOC; + rtmp_body->body = (uint8_t *) realloc( rtmp_body->body, rtmp_body->length_buffer ); + } + + memcpy( rtmp_body->body + rtmp_body->length_body, buffer, length ); + rtmp_body->length_body += length; +} + +/***************************************************************************** + * RTMP ping implementation: + ******************************************************************************/ +static uint8_t * +ping_encode( uint16_t type, uint32_t src_dst, uint32_t third_arg, uint32_t fourth_arg ) +{ + uint8_t *out; + + if( type == RTMP_PING_CLEAR_STREAM ) + out = (uint8_t *) malloc( RTMP_PING_SIZE_CLEAR_STREAM * sizeof( uint8_t ) ); + else if( type == RTMP_PING_CLEAR_PLAYING_BUFFER ) + out = (uint8_t *) malloc( RTMP_PING_SIZE_CLEAR_PLAYING_BUFFER * sizeof( uint8_t ) ); + else if( type == RTMP_PING_BUFFER_TIME_CLIENT ) + { + out = (uint8_t *) malloc( RTMP_PING_SIZE_BUFFER_TIME_CLIENT * sizeof( uint8_t ) ); + + third_arg = hton32( third_arg ); + memcpy( out + 6, &third_arg, sizeof( uint32_t ) ); + } +/* else if( type == RTMP_PING_RESET_STREAM ) TODO: research this + { + } + else if( type == RTMP_PING_CLIENT_FROM_SERVER ) + { + } + else if( type == RTMP_PING_PONG_FROM_CLIENT ) + { + } +*/ else + { + out = (uint8_t *) malloc( RTMP_PING_SIZE_BUFFER_TIME_CLIENT * sizeof( uint8_t ) ); + + out[6] = 0x0D; out[7] = 0x0E; out[8] = 0x0A; out[9] = 0x0D; + } + + type = hton16( type ); + memcpy( out, &type, sizeof( uint16_t ) ); + + src_dst = hton32( src_dst ); + memcpy( out + 2, &src_dst, sizeof( uint32_t ) ); + + return out; +} + +/***************************************************************************** + * AMF implementation: + ******************************************************************************/ +static uint8_t * +amf_encode_element( uint8_t element, const void *value ) +{ + uint8_t *out; + + if ( element == AMF_DATATYPE_NUMBER ) + { + uint64_t number = *(uint64_t *) value; + + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_NUMBER * sizeof( uint8_t ) ); + + number = hton64( number ); + out[0] = AMF_DATATYPE_NUMBER; + memcpy( out + 1, &number, sizeof( uint64_t ) ); + } else if ( element == AMF_DATATYPE_BOOLEAN ) + { + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_BOOLEAN * sizeof( uint8_t ) ); + + out[0] = AMF_DATATYPE_BOOLEAN; + out[1] = *(uint8_t *) value; + } else if ( element == AMF_DATATYPE_STRING ) + { + uint16_t length_psz, length_psz_cpy; + + length_psz = length_psz_cpy = strlen( (char *) value ); + out = (uint8_t *) malloc( ( AMF_DATATYPE_SIZE_STRING + length_psz ) * sizeof( uint8_t ) ); + + out[0] = AMF_DATATYPE_STRING; + length_psz = hton16( length_psz ); + memcpy( out + 1, &length_psz, sizeof( uint16_t ) ); + memcpy( out + 3, value, length_psz_cpy ); + } else if ( element == AMF_DATATYPE_OBJECT ) + { + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_OBJECT * sizeof( uint8_t ) ); + + out[0] = AMF_DATATYPE_OBJECT; + } else if ( element == AMF_DATATYPE_NULL ) + { + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_NULL * sizeof( uint8_t ) ); + + out[0] = AMF_DATATYPE_NULL; + } else if ( element == AMF_DATATYPE_MIXED_ARRAY ) + { + uint32_t highest_index = *(uint32_t *) value; + + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_MIXED_ARRAY * sizeof( uint8_t ) ); + + highest_index = hton32( highest_index ); + out[0] = AMF_DATATYPE_MIXED_ARRAY; + memcpy( out + 1, &highest_index, sizeof( uint32_t ) ); + } else if ( element == AMF_DATATYPE_END_OF_OBJECT ) + { + out = (uint8_t *) calloc( AMF_DATATYPE_SIZE_END_OF_OBJECT, sizeof( uint8_t ) ); + + out[AMF_DATATYPE_SIZE_END_OF_OBJECT - 1] = AMF_DATATYPE_END_OF_OBJECT; + } else + { + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_NUMBER * sizeof( uint8_t ) ); + + out[0] = AMF_DATATYPE_NUMBER; + out[1] = 0x0D; out[2] = 0x0E; out[3] = 0x0A; out[4] = 0x0D; + out[5] = 0x0B; out[6] = 0x0E; out[7] = 0x0E; out[8] = 0x0F; + } + + return out; +} + +static uint8_t * +amf_encode_object_variable( const char *key, uint8_t element, const void *value ) +{ + uint8_t *out, *out_value; + int length_value; + uint16_t length_psz, length_psz_cpy; + + length_psz = length_psz_cpy = strlen( key ); + + if( element == AMF_DATATYPE_NUMBER ) + length_value = AMF_DATATYPE_SIZE_NUMBER; + else if( element == AMF_DATATYPE_BOOLEAN ) + length_value = AMF_DATATYPE_SIZE_BOOLEAN; + else if( element == AMF_DATATYPE_STRING ) + length_value = AMF_DATATYPE_SIZE_STRING + strlen( (char *) value ); + else if( element == AMF_DATATYPE_NULL ) + length_value = AMF_DATATYPE_SIZE_NULL; + else + { + out = (uint8_t *) malloc( AMF_DATATYPE_SIZE_NUMBER * sizeof( uint8_t ) ); + + out[0] = AMF_DATATYPE_NUMBER; + out[1] = 0xD; out[2] = 0xE; out[3] = 0xA; out[4] = 0xD; + out[5] = 0xB; out[6] = 0xE; out[7] = 0xE; out[8] = 0xF; + + return out; + } + + out = (uint8_t *) malloc( ( AMF_DATATYPE_SIZE_OBJECT_VARIABLE + length_psz + length_value ) * sizeof( uint8_t ) ); + + length_psz = hton16( length_psz ); + memcpy( out, &length_psz, sizeof( uint16_t ) ); + memcpy( out + 2, key, length_psz_cpy ); + + out_value = amf_encode_element( element, value ); + memcpy( out + 2 + length_psz_cpy, out_value, length_value ); + free( out_value ); + + return out; +} + +static double +amf_decode_number( uint8_t **buffer ) +{ + uint64_t number; + double out; + + number = ntoh64( *(uint64_t *) *buffer ); + memcpy(&out, &number, sizeof( uint64_t ) ); + *buffer += sizeof( uint64_t ); + + return out; +} + +static int +amf_decode_boolean( uint8_t **buffer ) +{ + int out; + + out = **buffer; + *buffer += 1; + + return out; +} + +/* return value allocated dinamically */ +static char * +amf_decode_string( uint8_t **buffer ) +{ + char *out; + int length; + int i; + + length = ntoh16( *(uint16_t *) *buffer ); + *buffer += sizeof( uint16_t ); + + out = (char *) malloc( length + 1 ); /* '\0' terminated */ + + for(i = 0; i < length; i++) + out[i] = (*buffer)[i]; + + *buffer += length; + + out[i] = '\0'; + + return out; +} + +/* returns in each call next key, at end of object returns NULL */ +/* need to decode value of key after call */ +static char * +amf_decode_object( uint8_t **buffer ) +{ + if( **buffer == 0x0 && *(*buffer + 1) == 0x00 && *(*buffer + 2) == 0x09) + { + *buffer += 3; + + return NULL; + } + else + return amf_decode_string( buffer ); +} + +/***************************************************************************** + * FLV rebuilding implementation: + ******************************************************************************/ +static block_t * +flv_new_packet( rtmp_control_thread_t *p_thread, rtmp_packet_t *rtmp_packet ) +{ + block_t *p_buffer; + /* DOWN: p_thread->p_empty_blocks->i_depth */ + while ( block_FifoCount( p_thread->p_empty_blocks ) > MAX_EMPTY_BLOCKS ) + { + p_buffer = block_FifoGet( p_thread->p_empty_blocks ); + block_Release( p_buffer ); + } + /* DOWN: p_thread->p_empty_blocks->i_depth */ + if( block_FifoCount( p_thread->p_empty_blocks ) == 0 ) + { + p_buffer = block_New( p_thread, rtmp_packet->body->length_body ); + } + else + { + p_buffer = block_FifoGet( p_thread->p_empty_blocks ); + p_buffer = block_Realloc( p_buffer, 0, rtmp_packet->body->length_body ); + } + + p_buffer->i_buffer = rtmp_packet->body->length_body; + + memcpy( p_buffer->p_buffer, rtmp_packet->body->body, p_buffer->i_buffer ); + + return p_buffer; +} + +static void +flv_rebuild( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ) +{ + uint32_t length_tag, timestamp; + + rtmp_packet->body->body = (uint8_t *) realloc( rtmp_packet->body->body, + rtmp_packet->body->length_body + FLV_TAG_PREVIOUS_TAG_SIZE + FLV_TAG_SIZE ); + memmove( rtmp_packet->body->body + FLV_TAG_PREVIOUS_TAG_SIZE + FLV_TAG_SIZE, + rtmp_packet->body->body, rtmp_packet->body->length_body ); + + /* Insert tag */ + rtmp_control_thread->flv_tag_previous_tag_size = hton32( rtmp_control_thread->flv_tag_previous_tag_size ); + memcpy( rtmp_packet->body->body, &rtmp_control_thread->flv_tag_previous_tag_size, sizeof( uint32_t ) ); + + /* Fill backwards because of overlapping*/ + rtmp_packet->body->body[11] = 0x00; + + timestamp = hton32( rtmp_packet->timestamp ); + memcpy( rtmp_packet->body->body + 7, ×tamp, sizeof( uint32_t ) ); + + length_tag = hton32( rtmp_packet->body->length_body ); + memcpy( rtmp_packet->body->body + 4, &length_tag, sizeof( uint32_t ) ); + + rtmp_packet->body->body[4] = rtmp_packet->content_type; + + rtmp_packet->body->body[12] = 0x00; + rtmp_packet->body->body[13] = 0x00; + rtmp_packet->body->body[14] = 0x00; + + rtmp_control_thread->flv_tag_previous_tag_size = rtmp_packet->body->length_body + FLV_TAG_SIZE; + + /* Update size */ + rtmp_packet->body->length_body += FLV_TAG_PREVIOUS_TAG_SIZE + FLV_TAG_SIZE; + rtmp_packet->body->length_buffer = rtmp_packet->body->length_body; +} + +static void +flv_get_metadata_audio( rtmp_packet_t *packet_audio, uint8_t *stereo, uint8_t *audiosamplesize, uint8_t *audiosamplerate, uint8_t *audiocodecid ) +{ + uint8_t data_audio; + + data_audio = *(packet_audio->body->body + packet_audio->length_header); + + if( ( data_audio & FLV_AUDIO_STEREO_MASK ) == FLV_AUDIO_STEREO_MONO ) + *stereo = FLV_AUDIO_STEREO_MONO; + else if( ( data_audio & FLV_AUDIO_STEREO_MASK ) == FLV_AUDIO_STEREO_STEREO ) + *stereo = FLV_AUDIO_STEREO_STEREO; + + if( ( data_audio & FLV_AUDIO_SIZE_MASK ) == FLV_AUDIO_SIZE_8_BIT ) + *audiosamplesize = FLV_AUDIO_SIZE_8_BIT >> 1; + else if( ( data_audio & FLV_AUDIO_SIZE_MASK ) == FLV_AUDIO_SIZE_16_BIT ) + *audiosamplesize = FLV_AUDIO_SIZE_16_BIT >> 1; + + if( ( data_audio & FLV_AUDIO_RATE_MASK ) == FLV_AUDIO_RATE_5_5_KHZ ) + *audiosamplerate = FLV_AUDIO_RATE_5_5_KHZ >> 2; + else if( ( data_audio & FLV_AUDIO_RATE_MASK ) == FLV_AUDIO_RATE_11_KHZ ) + *audiosamplerate = FLV_AUDIO_RATE_11_KHZ >> 2; + else if( ( data_audio & FLV_AUDIO_RATE_MASK ) == FLV_AUDIO_RATE_22_KHZ ) + *audiosamplerate = FLV_AUDIO_RATE_22_KHZ >> 2; + else if( ( data_audio & FLV_AUDIO_RATE_MASK ) == FLV_AUDIO_RATE_44_KHZ ) + *audiosamplerate = FLV_AUDIO_RATE_44_KHZ >> 2; + + if( ( data_audio & FLV_AUDIO_CODEC_ID_MASK ) == FLV_AUDIO_CODEC_ID_UNCOMPRESSED ) + *audiocodecid = FLV_AUDIO_CODEC_ID_UNCOMPRESSED >> 4; + else if( ( data_audio & FLV_AUDIO_CODEC_ID_MASK ) == FLV_AUDIO_CODEC_ID_ADPCM ) + *audiocodecid = FLV_AUDIO_CODEC_ID_ADPCM >> 4; + else if( ( data_audio & FLV_AUDIO_CODEC_ID_MASK ) == FLV_AUDIO_CODEC_ID_MP3 ) + *audiocodecid = FLV_AUDIO_CODEC_ID_MP3 >> 4; + else if( ( data_audio & FLV_AUDIO_CODEC_ID_MASK ) == FLV_AUDIO_CODEC_ID_NELLYMOSER_8KHZ_MONO ) + *audiocodecid = FLV_AUDIO_CODEC_ID_NELLYMOSER_8KHZ_MONO >> 4; + else if( ( data_audio & FLV_AUDIO_CODEC_ID_MASK ) == FLV_AUDIO_CODEC_ID_NELLYMOSER ) + *audiocodecid = FLV_AUDIO_CODEC_ID_NELLYMOSER >> 4; +} + +static void +flv_get_metadata_video( rtmp_packet_t *packet_video, uint8_t *videocodecid, uint8_t *frametype ) +{ + uint8_t data_video; + + data_video = *(packet_video->body->body + packet_video->length_header); + + if( ( data_video & FLV_VIDEO_CODEC_ID_MASK ) == FLV_VIDEO_CODEC_ID_SORENSEN_H263 ) + *videocodecid = FLV_VIDEO_CODEC_ID_SORENSEN_H263; + else if( ( data_video & FLV_VIDEO_CODEC_ID_MASK ) == FLV_VIDEO_CODEC_ID_SCREEN_VIDEO ) + *videocodecid = FLV_VIDEO_CODEC_ID_SCREEN_VIDEO; + else if( ( data_video & FLV_VIDEO_CODEC_ID_MASK ) == FLV_VIDEO_CODEC_ID_ON2_VP6 ) + *videocodecid = FLV_VIDEO_CODEC_ID_ON2_VP6; + else if( ( data_video & FLV_VIDEO_CODEC_ID_MASK ) == FLV_VIDEO_CODEC_ID_ON2_VP6_ALPHA ) + *videocodecid = FLV_VIDEO_CODEC_ID_ON2_VP6_ALPHA; + else if( ( data_video & FLV_VIDEO_CODEC_ID_MASK ) == FLV_VIDEO_CODEC_ID_SCREEN_VIDEO_2 ) + *videocodecid = FLV_VIDEO_CODEC_ID_SCREEN_VIDEO_2; + + if( ( data_video & FLV_VIDEO_FRAME_TYPE_MASK ) == FLV_VIDEO_FRAME_TYPE_KEYFRAME ) + *frametype = FLV_VIDEO_FRAME_TYPE_KEYFRAME >> 4; + else if( ( data_video & FLV_VIDEO_FRAME_TYPE_MASK ) == FLV_VIDEO_FRAME_TYPE_INTER_FRAME ) + *frametype = FLV_VIDEO_FRAME_TYPE_INTER_FRAME >> 4; + else if( ( data_video & FLV_VIDEO_FRAME_TYPE_MASK ) == FLV_VIDEO_FRAME_TYPE_DISPOSABLE_INTER_FRAME ) + *frametype = FLV_VIDEO_FRAME_TYPE_DISPOSABLE_INTER_FRAME >> 4; +} + +static rtmp_packet_t * +flv_build_onMetaData( access_t *p_access, uint64_t duration, uint8_t stereo, uint8_t audiosamplesize, uint8_t audiosamplerate, uint8_t audiocodecid, uint8_t videocodecid ) +{ + rtmp_packet_t *rtmp_packet; + rtmp_body_t *rtmp_body; + uint8_t *tmp_buffer; + uint64_t number; + + rtmp_body = rtmp_body_new(); + + tmp_buffer = amf_encode_element( AMF_DATATYPE_STRING, "onMetaData" ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_STRING + strlen( "onMetaData" ) ); + free( tmp_buffer ); + + number = 0; + tmp_buffer = amf_encode_element( AMF_DATATYPE_MIXED_ARRAY, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_MIXED_ARRAY ); + free( tmp_buffer ); + + number = duration; + tmp_buffer = amf_encode_object_variable( "duration", + AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "duration" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + tmp_buffer = amf_encode_object_variable( "stereo", + AMF_DATATYPE_BOOLEAN, &stereo ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "stereo" ) + + AMF_DATATYPE_SIZE_BOOLEAN ); + free( tmp_buffer ); + + number = audiosamplesize; + tmp_buffer = amf_encode_object_variable( "audiosamplesize", + AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "audiosamplesize" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + number = audiosamplerate; + tmp_buffer = amf_encode_object_variable( "audiosamplerate", + AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "audiosamplerate" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + number = audiocodecid; + tmp_buffer = amf_encode_object_variable( "audiocodecid", + AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "audiocodecid" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + number = videocodecid; + tmp_buffer = amf_encode_object_variable( "videocodecid", + AMF_DATATYPE_NUMBER, &number ); + rtmp_body_append( rtmp_body, tmp_buffer, + AMF_DATATYPE_SIZE_OBJECT_VARIABLE + strlen( "videocodecid" ) + + AMF_DATATYPE_SIZE_NUMBER ); + free( tmp_buffer ); + + + tmp_buffer = amf_encode_element( AMF_DATATYPE_END_OF_OBJECT, NULL ); + rtmp_body_append( rtmp_body, tmp_buffer, AMF_DATATYPE_SIZE_END_OF_OBJECT ); + free( tmp_buffer ); + + rtmp_packet = rtmp_new_packet( p_access->p_sys->p_thread, RTMP_AMF_STREAM_INDEX_DEFAULT, + 0, RTMP_CONTENT_TYPE_NOTIFY, 0, rtmp_body ); + free( rtmp_body->body ); + free( rtmp_body ); + + return rtmp_packet; +} + +block_t * +flv_get_metadata( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + rtmp_packet_t *flv_metadata_packet; + block_t *p_buffer; + + flv_metadata_packet = flv_build_onMetaData( p_access, 0, p_sys->p_thread->metadata_stereo, + p_sys->p_thread->metadata_samplesize, p_sys->p_thread->metadata_samplerate, + p_sys->p_thread->metadata_audiocodecid, p_sys->p_thread->metadata_videocodecid ); + flv_rebuild( p_sys->p_thread, flv_metadata_packet ); + p_buffer = flv_new_packet( p_sys->p_thread, flv_metadata_packet ); + + free( flv_metadata_packet->body->body ); + free( flv_metadata_packet->body ); + free( flv_metadata_packet ); + + return p_buffer; +} + +block_t * +flv_insert_header( access_t *p_access, block_t *first_packet ) +{ + access_sys_t *p_sys = p_access->p_sys; + int old_buffer_size; + uint32_t tmp_number; + + old_buffer_size = first_packet->i_buffer; + + first_packet = block_Realloc( first_packet, 0, first_packet->i_buffer + FLV_HEADER_SIZE ); + + memmove( first_packet->p_buffer + FLV_HEADER_SIZE, + first_packet->p_buffer, old_buffer_size ); + + memcpy( first_packet->p_buffer, FLV_HEADER_SIGNATURE, sizeof( FLV_HEADER_SIGNATURE ) ); + first_packet->p_buffer[3] = FLV_HEADER_VERSION; + if( p_sys->p_thread->has_audio && p_sys->p_thread->has_video ) + first_packet->p_buffer[4] = FLV_HEADER_AUDIO | FLV_HEADER_VIDEO; + else if( p_sys->p_thread->has_audio ) + first_packet->p_buffer[4] = FLV_HEADER_AUDIO; + else + first_packet->p_buffer[4] = FLV_HEADER_VIDEO; + tmp_number = hton32( FLV_HEADER_SIZE ); + memcpy( first_packet->p_buffer + 5, &tmp_number, sizeof( uint32_t ) ); + + return first_packet; +} diff --git a/modules/access/rtmp/rtmp_amf_flv.h b/modules/access/rtmp/rtmp_amf_flv.h new file mode 100644 index 0000000000..79d04bc753 --- /dev/null +++ b/modules/access/rtmp/rtmp_amf_flv.h @@ -0,0 +1,126 @@ +/***************************************************************************** + * rtmp_amf_flv.h: RTMP, AMF and FLV over RTMP implementation. + ***************************************************************************** + * Copyright (C) URJC - LADyR - Luis Lopez Fernandez + * + * Author: Miguel Angel Cabrera Moya + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Local prototypes (continued from access.c) + *****************************************************************************/ +typedef struct rtmp_packet_t rtmp_packet_t; +typedef struct rtmp_body_t rtmp_body_t; +typedef struct rtmp_control_thread_t rtmp_control_thread_t; +typedef void (*rtmp_handler_t)( rtmp_control_thread_t *rtmp_control_thread, rtmp_packet_t *rtmp_packet ); + +struct rtmp_packet_t +{ + int length_header; + int stream_index; + uint32_t timestamp; + uint32_t timestamp_relative; + int32_t length_encoded; + int32_t length_body; + uint8_t content_type; + uint32_t src_dst; + rtmp_body_t *body; +}; + +struct rtmp_body_t +{ + int32_t length_body; /* without interchunk headers */ + int32_t length_buffer; + uint8_t *body; +}; + +struct rtmp_control_thread_t +{ + VLC_COMMON_MEMBERS + + int fd; + + block_fifo_t *p_fifo_media; + block_fifo_t *p_empty_blocks; + + vlc_mutex_t lock; + vlc_cond_t wait; + + int result_connect; + int result_play; + int result_publish; + + double stream_client; + double stream_server; + + char *psz_publish; + + /* Rebuild FLV variables */ + int has_audio; + int has_video; + int metadata_received; + uint8_t metadata_stereo; + uint8_t metadata_samplesize; + uint8_t metadata_samplerate; + uint8_t metadata_audiocodecid; + uint8_t metadata_videocodecid; + uint8_t metadata_frametype; + int first_media_packet; + uint32_t flv_tag_previous_tag_size; + + /* vars for channel state */ + rtmp_packet_t rtmp_headers_recv[64]; /* RTMP_HEADER_STREAM_MAX */ + rtmp_packet_t rtmp_headers_send[64]; + + rtmp_handler_t rtmp_handler[21]; /* index by RTMP_CONTENT_TYPE */ +}; + +struct access_sys_t +{ + int active; + + int fd; + + vlc_url_t url; + char *psz_application; + char *psz_media; + + /* vars for reading from fifo */ + block_t *flv_packet; + int read_packet; + + /* thread for filtering and handling control messages */ + rtmp_control_thread_t *p_thread; +}; + +/***************************************************************************** + * RTMP header: + ******************************************************************************/ +int rtmp_handshake_passive( vlc_object_t *p_this ); +int rtmp_handshake_active( vlc_object_t *p_this ); +int rtmp_connect_active( vlc_object_t *p_this ); +//int rtmp_seek( access_t *p_access, int64_t i_pos ); TODO +int rtmp_send_bytes_read( access_t *p_access, uint32_t reply ); +int rtmp_send_publish_start( access_t *p_access ); + +rtmp_packet_t *rtmp_read_net_packet( rtmp_control_thread_t *p_thread ); +void rtmp_init_handler( rtmp_handler_t *rtmp_handler ); +/***************************************************************************** + * FLV header: + ******************************************************************************/ +block_t *flv_get_metadata( access_t *p_access ); +block_t *flv_insert_header( access_t *p_access, block_t *first_packet );