RTMP as easy as A.B.C. Avant-Garde Solutions Inc. -- Barracuda Networks Inc. -- ClueCon! http://www.cluecon.com
This commit is contained in:
parent
2109627510
commit
0933a34369
|
@ -68,6 +68,7 @@ endpoints/mod_loopback
|
|||
#endpoints/mod_skypopen
|
||||
#endpoints/mod_h323
|
||||
#endpoints/mod_khomp
|
||||
#endpoints/mod_rtmp
|
||||
#../../libs/openzap/mod_openzap
|
||||
#../../libs/freetdm/mod_freetdm
|
||||
#asr_tts/mod_unimrcp
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<!-- <load module="mod_unicall"/> -->
|
||||
<!-- <load module="mod_skinny"/> -->
|
||||
<!-- <load module="mod_khomp"/> -->
|
||||
<!-- <load module="mod_rtmp"/> -->
|
||||
|
||||
<!-- Applications -->
|
||||
<load module="mod_commands"/>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
BASE=../../../..
|
||||
|
||||
LIBAMF_OBJS=libamf/src/amf0.o libamf/src/hash.o libamf/src/io.o libamf/src/ptrarray.o libamf/src/types.o
|
||||
LOCAL_OBJS=rtmp_sig.o rtmp.o rtmp_tcp.o $(LIBAMF_OBJS)
|
||||
LOCAL_CFLAGS=-Ilibamf/src
|
||||
|
||||
include $(BASE)/build/modmake.rules
|
|
@ -0,0 +1,64 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(libamf C)
|
||||
|
||||
# generic variables
|
||||
set(PACKAGE "libamf")
|
||||
set(PACKAGE_NAME ${PACKAGE})
|
||||
set(PACKAGE_BUGREPORT "libamf@sf.net")
|
||||
set(PACKAGE_VERSION "0.1.0")
|
||||
set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
|
||||
|
||||
#platform tests
|
||||
include(CheckFunctionExists)
|
||||
include(CheckIncludeFile)
|
||||
include(CheckTypeSize)
|
||||
include(TestBigEndian)
|
||||
|
||||
check_include_file(sys/types.h HAVE_SYS_TYPES_H)
|
||||
check_include_file(stdint.h HAVE_STDINT_H)
|
||||
check_include_file(stddef.h HAVE_STDDEF_H)
|
||||
check_include_file(inttypes.h HAVE_INTTYPES_H)
|
||||
|
||||
check_type_size("double" SIZEOF_DOUBLE)
|
||||
check_type_size("float" SIZEOF_FLOAT)
|
||||
check_type_size("long double" SIZEOF_LONG_DOUBLE)
|
||||
|
||||
if(WIN32)
|
||||
set(int16_t 1)
|
||||
set(int32_t 1)
|
||||
set(int64_t 1)
|
||||
set(int8_t 1)
|
||||
set(uint16_t 1)
|
||||
set(uint32_t 1)
|
||||
set(uint64_t 1)
|
||||
set(uint8_t 1)
|
||||
endif(WIN32)
|
||||
|
||||
test_big_endian(IS_BIGENDIAN)
|
||||
if(IS_BIGENDIAN)
|
||||
set(WORDS_BIGENDIAN 1)
|
||||
endif(IS_BIGENDIAN)
|
||||
|
||||
# configuration file
|
||||
configure_file(amf-cmake.h.in ${CMAKE_BINARY_DIR}/amf.h)
|
||||
include_directories(${CMAKE_BINARY_DIR})
|
||||
|
||||
install(
|
||||
FILES ${CMAKE_BINARY_DIR}/amf.h
|
||||
DESTINATION include/amf
|
||||
)
|
||||
|
||||
# Visual C++ specific configuration
|
||||
if(MSVC)
|
||||
# use static library
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT")
|
||||
|
||||
# C runtime deprecation in Visual C++ 2005 and later
|
||||
add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
|
||||
endif(MSVC)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# tests
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
|
@ -0,0 +1,105 @@
|
|||
#ifndef __AMF_H__
|
||||
#define __AMF_H__
|
||||
|
||||
/* Name of package */
|
||||
#define PACKAGE "@PACKAGE@"
|
||||
|
||||
/* Define to the address where bug reports for this package should be sent. */
|
||||
#define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@"
|
||||
|
||||
/* Define to the full name of this package. */
|
||||
#define PACKAGE_NAME "@PACKAGE_NAME@"
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#define PACKAGE_STRING "@PACKAGE_STRING@"
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#define PACKAGE_TARNAME "@PACKAGE_NAME@"
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#define PACKAGE_VERSION "@PACKAGE_VERSION@"
|
||||
|
||||
/* The size of `double', as computed by sizeof. */
|
||||
#define SIZEOF_DOUBLE @SIZEOF_DOUBLE@
|
||||
|
||||
/* The size of `float', as computed by sizeof. */
|
||||
#define SIZEOF_FLOAT @SIZEOF_FLOAT@
|
||||
|
||||
/* The size of `long double', as computed by sizeof. */
|
||||
#define SIZEOF_LONG_DOUBLE @SIZEOF_LONG_DOUBLE@
|
||||
|
||||
/* Version number of package */
|
||||
#define VERSION "@PACKAGE_VERSION@"
|
||||
|
||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||
#cmakedefine HAVE_SYS_TYPES_H
|
||||
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#cmakedefine HAVE_STDINT_H
|
||||
|
||||
/* Define to 1 if you have the <stddef.h> header file. */
|
||||
#cmakedefine HAVE_STDDEF_H
|
||||
|
||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||
#cmakedefine HAVE_INTTYPES_H
|
||||
|
||||
/* Define to 1 if your processor stores words with the most significant byte
|
||||
first (like Motorola and SPARC, unlike Intel and VAX). */
|
||||
#cmakedefine WORDS_BIGENDIAN
|
||||
|
||||
/* Define to the type of an integer type of width exactly 16 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine int16_t short int
|
||||
|
||||
/* Define to the type of an integer type of width exactly 32 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine int32_t int
|
||||
|
||||
/* Define to the type of an integer type of width exactly 64 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine int64_t long long int
|
||||
|
||||
/* Define to the type of an integer type of width exactly 8 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine int8_t char
|
||||
|
||||
/* Define to the type of an unsigned integer type of width exactly 16 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine uint16_t unsigned short int
|
||||
|
||||
/* Define to the type of an unsigned integer type of width exactly 32 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine uint32_t unsigned int
|
||||
|
||||
/* Define to the type of an unsigned integer type of width exactly 64 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine uint64_t unsigned long long int
|
||||
|
||||
/* Define to the type of an unsigned integer type of width exactly 8 bits if
|
||||
such a type exists and the standard includes do not define it. */
|
||||
#cmakedefine uint8_t unsigned char
|
||||
|
||||
#ifdef HAVE_INTTYPES_H
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* AMF number */
|
||||
typedef
|
||||
#if SIZEOF_FLOAT == 8
|
||||
float
|
||||
#elif SIZEOF_DOUBLE == 8
|
||||
double
|
||||
#elif SIZEOF_LONG_DOUBLE == 8
|
||||
long double
|
||||
#else
|
||||
uint64_t
|
||||
#endif
|
||||
number64_t;
|
||||
|
||||
/* custom read/write function type */
|
||||
typedef size_t (*read_proc_t)(void * out_buffer, size_t size, void * user_data);
|
||||
typedef size_t (*write_proc_t)(const void * in_buffer, size_t size, void * user_data);
|
||||
|
||||
#endif /* __AMF_H__ */
|
|
@ -0,0 +1,26 @@
|
|||
set(libamf_src
|
||||
amf0.c
|
||||
amf0.h
|
||||
hash.c
|
||||
hash.h
|
||||
io.c
|
||||
io.h
|
||||
ptrarray.c
|
||||
ptrarray.h
|
||||
types.c
|
||||
types.h
|
||||
${CMAKE_BINARY_DIR}/amf.h
|
||||
)
|
||||
|
||||
add_library(amf ${libamf_src})
|
||||
|
||||
install(TARGETS amf
|
||||
RUNTIME DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
)
|
||||
|
||||
install(
|
||||
FILES amf0.h amf3.h
|
||||
DESTINATION include/amf
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef __AMF_H__
|
||||
#define __AMF_H__
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
/* AMF number */
|
||||
typedef double number64_t;
|
||||
|
||||
/* custom read/write function type */
|
||||
typedef size_t (*read_proc_t)(void * out_buffer, size_t size, void * user_data);
|
||||
typedef size_t (*write_proc_t)(const void * in_buffer, size_t size, void * user_data);
|
||||
|
||||
#endif /* __AMF_H__ */
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,190 @@
|
|||
#ifndef __AMF0_H__
|
||||
#define __AMF0_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "amf.h"
|
||||
|
||||
/* AMF data types */
|
||||
#define AMF0_TYPE_NUMBER 0x00
|
||||
#define AMF0_TYPE_BOOLEAN 0x01
|
||||
#define AMF0_TYPE_STRING 0x02
|
||||
#define AMF0_TYPE_OBJECT 0x03
|
||||
#define AMF0_TYPE_MOVIECLIP 0x04 /* reserved, not supported */
|
||||
#define AMF0_TYPE_NULL 0x05
|
||||
#define AMF0_TYPE_UNDEFINED 0x06
|
||||
#define AMF0_TYPE_REFERENCE 0x07
|
||||
#define AMF0_TYPE_ECMA_ARRAY 0x08
|
||||
#define AMF0_TYPE_OBJECT_END 0x09
|
||||
#define AMF0_TYPE_STRICT_ARRAY 0x0A
|
||||
#define AMF0_TYPE_DATE 0x0B
|
||||
#define AMF0_TYPE_LONG_STRING 0x0C
|
||||
#define AMF0_TYPE_UNSUPPORTED 0x0D
|
||||
#define AMF0_TYPE_RECORDSET 0x0E /* reserved, not supported */
|
||||
#define AMF0_TYPE_XML_DOCUMENT 0x0F
|
||||
#define AMF0_TYPE_TYPED_OBJECT 0x10
|
||||
|
||||
typedef struct __amf0_node * p_amf0_node;
|
||||
|
||||
/* string type */
|
||||
typedef struct __amf0_string {
|
||||
uint16_t size;
|
||||
uint8_t * mbstr;
|
||||
} amf0_string;
|
||||
|
||||
/* array type */
|
||||
typedef struct __amf0_list {
|
||||
uint32_t size;
|
||||
p_amf0_node first_element;
|
||||
p_amf0_node last_element;
|
||||
} amf0_list;
|
||||
|
||||
/* date type */
|
||||
typedef struct __amf0_date {
|
||||
number64_t milliseconds;
|
||||
int16_t timezone;
|
||||
} amf0_date;
|
||||
|
||||
/* XML string type */
|
||||
typedef struct __amf0_xmlstring {
|
||||
uint32_t size;
|
||||
uint8_t * mbstr;
|
||||
} amf0_xmlstring;
|
||||
|
||||
/* class type */
|
||||
typedef struct __amf0_class {
|
||||
amf0_string name;
|
||||
amf0_list elements;
|
||||
} amf0_class;
|
||||
|
||||
/* structure encapsulating the various AMF objects */
|
||||
typedef struct __amf0_data {
|
||||
uint8_t type;
|
||||
union {
|
||||
number64_t number_data;
|
||||
uint8_t boolean_data;
|
||||
amf0_string string_data;
|
||||
amf0_list list_data;
|
||||
amf0_date date_data;
|
||||
amf0_xmlstring xmlstring_data;
|
||||
amf0_class class_data;
|
||||
} u;
|
||||
} amf0_data;
|
||||
|
||||
/* node used in lists, relies on amf0_data */
|
||||
typedef struct __amf0_node {
|
||||
amf0_data * data;
|
||||
p_amf0_node prev;
|
||||
p_amf0_node next;
|
||||
} amf0_node;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* read AMF data */
|
||||
amf0_data * amf0_data_read(read_proc_t read_proc, void * user_data);
|
||||
|
||||
/* write AMF data */
|
||||
size_t amf0_data_write(amf0_data * data, write_proc_t write_proc, void * user_data);
|
||||
|
||||
/* generic functions */
|
||||
|
||||
/* allocate an AMF data object */
|
||||
amf0_data * amf0_data_new(uint8_t type);
|
||||
/* load AMF data from buffer */
|
||||
amf0_data * amf0_data_buffer_read(uint8_t * buffer, size_t maxbytes);
|
||||
/* load AMF data from stream */
|
||||
amf0_data * amf0_data_file_read(FILE * stream);
|
||||
/* AMF data size */
|
||||
size_t amf0_data_size(amf0_data * data);
|
||||
/* write encoded AMF data into a buffer */
|
||||
size_t amf0_data_buffer_write(amf0_data * data, uint8_t * buffer, size_t maxbytes);
|
||||
/* write encoded AMF data into a stream */
|
||||
size_t amf0_data_file_write(amf0_data * data, FILE * stream);
|
||||
/* get the type of AMF data */
|
||||
uint8_t amf0_data_get_type(amf0_data * data);
|
||||
/* return a new copy of AMF data */
|
||||
amf0_data * amf0_data_clone(amf0_data * data);
|
||||
/* release the memory of AMF data */
|
||||
void amf0_data_free(amf0_data * data);
|
||||
/* dump AMF data into a stream as text */
|
||||
void amf0_data_dump(FILE * stream, amf0_data * data, int indent_level);
|
||||
|
||||
/* number functions */
|
||||
amf0_data * amf0_number_new(number64_t value);
|
||||
number64_t amf0_number_get_value(amf0_data * data);
|
||||
void amf0_number_set_value(amf0_data * data, number64_t value);
|
||||
|
||||
/* boolean functions */
|
||||
amf0_data * amf0_boolean_new(uint8_t value);
|
||||
uint8_t amf0_boolean_get_value(amf0_data * data);
|
||||
void amf0_boolean_set_value(amf0_data * data, uint8_t value);
|
||||
|
||||
/* string functions */
|
||||
amf0_data * amf0_string_new(uint8_t * str, uint16_t size);
|
||||
amf0_data * amf0_str(const char * str);
|
||||
uint16_t amf0_string_get_size(amf0_data * data);
|
||||
uint8_t * amf0_string_get_uint8_ts(amf0_data * data);
|
||||
|
||||
/* object functions */
|
||||
amf0_data * amf0_object_new(void);
|
||||
uint32_t amf0_object_size(amf0_data * data);
|
||||
amf0_data * amf0_object_add(amf0_data * data, const char * name, amf0_data * element);
|
||||
amf0_data * amf0_object_get(amf0_data * data, const char * name);
|
||||
amf0_data * amf0_object_set(amf0_data * data, const char * name, amf0_data * element);
|
||||
amf0_data * amf0_object_delete(amf0_data * data, const char * name);
|
||||
amf0_node * amf0_object_first(amf0_data * data);
|
||||
amf0_node * amf0_object_last(amf0_data * data);
|
||||
amf0_node * amf0_object_next(amf0_node * node);
|
||||
amf0_node * amf0_object_prev(amf0_node * node);
|
||||
amf0_data * amf0_object_get_name(amf0_node * node);
|
||||
amf0_data * amf0_object_get_data(amf0_node * node);
|
||||
|
||||
/* null functions */
|
||||
#define amf0_null_new() amf0_data_new(AMF0_TYPE_NULL)
|
||||
|
||||
/* undefined functions */
|
||||
#define amf0_undefined_new() amf0_data_new(AMF0_TYPE_UNDEFINED)
|
||||
|
||||
/* associative array functions */
|
||||
amf0_data * amf0_associative_array_new(void);
|
||||
#define amf0_associative_array_size(d) amf0_object_size(d)
|
||||
#define amf0_associative_array_add(d, n, e) amf0_object_add(d, n, e)
|
||||
#define amf0_associative_array_get(d, n) amf0_object_get(d, n)
|
||||
#define amf0_associative_array_set(d, n, e) amf0_object_set(d, n, e)
|
||||
#define amf0_associative_array_delete(d, n) amf0_object_delete(d, n)
|
||||
#define amf0_associative_array_first(d) amf0_object_first(d)
|
||||
#define amf0_associative_array_last(d) amf0_object_last(d)
|
||||
#define amf0_associative_array_next(n) amf0_object_next(n)
|
||||
#define amf0_associative_array_prev(n) amf0_object_prev(n)
|
||||
#define amf0_associative_array_get_name(n) amf0_object_get_name(n)
|
||||
#define amf0_associative_array_get_data(n) amf0_object_get_data(n)
|
||||
|
||||
/* array functions */
|
||||
amf0_data * amf0_array_new(void);
|
||||
uint32_t amf0_array_size(amf0_data * data);
|
||||
amf0_data * amf0_array_push(amf0_data * data, amf0_data * element);
|
||||
amf0_data * amf0_array_pop(amf0_data * data);
|
||||
amf0_node * amf0_array_first(amf0_data * data);
|
||||
amf0_node * amf0_array_last(amf0_data * data);
|
||||
amf0_node * amf0_array_next(amf0_node * node);
|
||||
amf0_node * amf0_array_prev(amf0_node * node);
|
||||
amf0_data * amf0_array_get(amf0_node * node);
|
||||
amf0_data * amf0_array_get_at(amf0_data * data, uint32_t n);
|
||||
amf0_data * amf0_array_delete(amf0_data * data, amf0_node * node);
|
||||
amf0_data * amf0_array_insert_before(amf0_data * data, amf0_node * node, amf0_data * element);
|
||||
amf0_data * amf0_array_insert_after(amf0_data * data, amf0_node * node, amf0_data * element);
|
||||
|
||||
/* date functions */
|
||||
amf0_data * amf0_date_new(number64_t milliseconds, int16_t timezone);
|
||||
number64_t amf0_date_get_milliseconds(amf0_data * data);
|
||||
int16_t amf0_date_get_timezone(amf0_data * data);
|
||||
time_t amf0_date_to_time_t(amf0_data * data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __AMF0_H__ */
|
|
@ -0,0 +1,130 @@
|
|||
/* function common to all array types */
|
||||
static void amf_list_init(amf_list * list) {
|
||||
if (list != NULL) {
|
||||
list->size = 0;
|
||||
list->first_element = NULL;
|
||||
list->last_element = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static amf_data * amf_list_push(amf_list * list, amf_data * data) {
|
||||
amf_node * node = (amf_node*)malloc(sizeof(amf_node));
|
||||
if (node != NULL) {
|
||||
node->data = data;
|
||||
node->next = NULL;
|
||||
node->prev = NULL;
|
||||
if (list->size == 0) {
|
||||
list->first_element = node;
|
||||
list->last_element = node;
|
||||
}
|
||||
else {
|
||||
list->last_element->next = node;
|
||||
node->prev = list->last_element;
|
||||
list->last_element = node;
|
||||
}
|
||||
++(list->size);
|
||||
return data;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static amf_data * amf_list_insert_before(amf_list * list, amf_node * node, amf_data * data) {
|
||||
if (node != NULL) {
|
||||
amf_node * new_node = (amf_node*)malloc(sizeof(amf_node));
|
||||
if (new_node != NULL) {
|
||||
new_node->next = node;
|
||||
new_node->prev = node->prev;
|
||||
|
||||
if (node->prev != NULL) {
|
||||
node->prev->next = new_node;
|
||||
node->prev = new_node;
|
||||
}
|
||||
if (node == list->first_element) {
|
||||
list->first_element = new_node;
|
||||
}
|
||||
++(list->size);
|
||||
new_node->data = data;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static amf_data * amf_list_insert_after(amf_list * list, amf_node * node, amf_data * data) {
|
||||
if (node != NULL) {
|
||||
amf_node * new_node = (amf_node*)malloc(sizeof(amf_node));
|
||||
if (new_node != NULL) {
|
||||
new_node->next = node->next;
|
||||
new_node->prev = node;
|
||||
|
||||
if (node->next != NULL) {
|
||||
node->next->prev = new_node;
|
||||
node->next = new_node;
|
||||
}
|
||||
if (node == list->last_element) {
|
||||
list->last_element = new_node;
|
||||
}
|
||||
++(list->size);
|
||||
new_node->data = data;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static amf_data * amf_list_delete(amf_list * list, amf_node * node) {
|
||||
amf_data * data = NULL;
|
||||
if (node != NULL) {
|
||||
if (node->next != NULL) {
|
||||
node->next->prev = node->prev;
|
||||
}
|
||||
if (node->prev != NULL) {
|
||||
node->prev->next = node->next;
|
||||
}
|
||||
if (node == list->first_element) {
|
||||
list->first_element = node->next;
|
||||
}
|
||||
if (node == list->last_element) {
|
||||
list->last_element = node->prev;
|
||||
}
|
||||
data = node->data;
|
||||
free(node);
|
||||
--(list->size);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static amf_data * amf_list_get_at(amf_list * list, uint32 n) {
|
||||
if (n < list->size) {
|
||||
uint32 i;
|
||||
amf_node * node = list->first_element;
|
||||
for (i = 0; i < n; ++i) {
|
||||
node = node->next;
|
||||
}
|
||||
return node->data;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static amf_data * amf_list_pop(amf_list * list) {
|
||||
return amf_list_delete(list, list->last_element);
|
||||
}
|
||||
|
||||
static amf_node * amf_list_first(amf_list * list) {
|
||||
return list->first_element;
|
||||
}
|
||||
|
||||
static amf_node * amf_list_last(amf_list * list) {
|
||||
return list->last_element;
|
||||
}
|
||||
|
||||
static void amf_list_clear(amf_list * list) {
|
||||
amf_node * node = list->first_element;
|
||||
while (node != NULL) {
|
||||
amf_data_free(node->data);
|
||||
amf_node * tmp = node;
|
||||
node = node->next;
|
||||
free(tmp);
|
||||
}
|
||||
list->size = 0;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
typedef struct __amf_list {
|
||||
uint32 size;
|
||||
p_amf_node first_element;
|
||||
p_amf_node last_element;
|
||||
} amf_list;
|
|
@ -0,0 +1,312 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hash.h"
|
||||
|
||||
#define assert(x)
|
||||
|
||||
/*static void *malloc_and_zero(int n){
|
||||
void *p = malloc(n);
|
||||
if( p ){
|
||||
memset(p, 0, n);
|
||||
}
|
||||
return p;
|
||||
}*/
|
||||
|
||||
/**
|
||||
Create a hash table
|
||||
*/
|
||||
Hash * HashCreate(char copyKey) {
|
||||
return HashCreateAlloc(copyKey, malloc, free);
|
||||
}
|
||||
|
||||
Hash * HashCreateAlloc(char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *)) {
|
||||
Hash * pHash = (Hash*)xMalloc(sizeof(Hash));
|
||||
if (pHash != NULL) {
|
||||
HashInit(pHash, copyKey, xMalloc, xFree);
|
||||
return pHash;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Erase the hash table
|
||||
*/
|
||||
void HashFree(Hash* pHash) {
|
||||
pHash->xFree(pHash);
|
||||
}
|
||||
|
||||
/*
|
||||
insert a string key element
|
||||
*/
|
||||
void * HashInsertSz(Hash *pH, const char *pKey, void *data) {
|
||||
return HashInsert(pH, pKey, (int) strlen(pKey)+1, data);
|
||||
}
|
||||
|
||||
/*
|
||||
find a string key element
|
||||
*/
|
||||
void * HashFindSz(const Hash* pH, const char *pKey) {
|
||||
return HashFind(pH, pKey, (int) strlen(pKey)+1);
|
||||
}
|
||||
|
||||
/* Turn bulk memory into a hash table object by initializing the
|
||||
** fields of the Hash structure.
|
||||
**
|
||||
** "pNew" is a pointer to the hash table that is to be initialized.
|
||||
** "copyKey" is true if the hash table should make its own private
|
||||
** copy of keys and false if it should just use the supplied pointer.
|
||||
*/
|
||||
void HashInit(Hash* pNew, char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *)){
|
||||
assert( pNew!=0 );
|
||||
pNew->copyKey = copyKey;
|
||||
pNew->first = 0;
|
||||
pNew->count = 0;
|
||||
pNew->htsize = 0;
|
||||
pNew->ht = 0;
|
||||
pNew->xMalloc = xMalloc;
|
||||
pNew->xFree = xFree;
|
||||
}
|
||||
|
||||
/* Remove all entries from a hash table. Reclaim all memory.
|
||||
** Call this routine to delete a hash table or to reset a hash table
|
||||
** to the empty state.
|
||||
*/
|
||||
void HashClear(Hash *pH){
|
||||
HashElem *elem; /* For looping over all elements of the table */
|
||||
|
||||
assert( pH!=0 );
|
||||
elem = pH->first;
|
||||
pH->first = 0;
|
||||
if( pH->ht ) pH->xFree(pH->ht);
|
||||
pH->ht = 0;
|
||||
pH->htsize = 0;
|
||||
while( elem ){
|
||||
HashElem *next_elem = elem->next;
|
||||
if( pH->copyKey && elem->pKey ){
|
||||
pH->xFree(elem->pKey);
|
||||
}
|
||||
pH->xFree(elem);
|
||||
elem = next_elem;
|
||||
}
|
||||
pH->count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Hash and comparison functions
|
||||
*/
|
||||
static int binHash(const void *pKey, int nKey){
|
||||
int h = 0;
|
||||
const char *z = (const char *)pKey;
|
||||
while( nKey-- > 0 ){
|
||||
h = (h<<3) ^ h ^ *(z++);
|
||||
}
|
||||
return h & 0x7fffffff;
|
||||
}
|
||||
|
||||
static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){
|
||||
if( n1!=n2 ) return 1;
|
||||
return memcmp(pKey1,pKey2,n1);
|
||||
}
|
||||
|
||||
/* Link an element into the hash table
|
||||
*/
|
||||
static void insertElement(
|
||||
Hash *pH, /* The complete hash table */
|
||||
struct _ht *pEntry, /* The entry into which pNew is inserted */
|
||||
HashElem *pNew /* The element to be inserted */
|
||||
){
|
||||
HashElem *pHead; /* First element already in pEntry */
|
||||
pHead = pEntry->chain;
|
||||
if( pHead ){
|
||||
pNew->next = pHead;
|
||||
pNew->prev = pHead->prev;
|
||||
if( pHead->prev ){ pHead->prev->next = pNew; }
|
||||
else { pH->first = pNew; }
|
||||
pHead->prev = pNew;
|
||||
}else{
|
||||
pNew->next = pH->first;
|
||||
if( pH->first ){ pH->first->prev = pNew; }
|
||||
pNew->prev = 0;
|
||||
pH->first = pNew;
|
||||
}
|
||||
pEntry->count++;
|
||||
pEntry->chain = pNew;
|
||||
}
|
||||
|
||||
|
||||
/* Resize the hash table so that it cantains "new_size" buckets.
|
||||
** "new_size" must be a power of 2. The hash table might fail
|
||||
** to resize if malloc fails.
|
||||
*/
|
||||
static void rehash(Hash *pH, int new_size){
|
||||
struct _ht *new_ht; /* The new hash table */
|
||||
HashElem *elem, *next_elem; /* For looping over existing elements */
|
||||
|
||||
assert( (new_size & (new_size-1))==0 );
|
||||
new_ht = (struct _ht *)pH->xMalloc( new_size*sizeof(struct _ht) );
|
||||
if( new_ht==0 ) return;
|
||||
if( pH->ht ) pH->xFree(pH->ht);
|
||||
pH->ht = new_ht;
|
||||
pH->htsize = new_size;
|
||||
for(elem=pH->first, pH->first=0; elem; elem = next_elem){
|
||||
int h = binHash(elem->pKey, elem->nKey) & (new_size-1);
|
||||
next_elem = elem->next;
|
||||
insertElement(pH, &new_ht[h], elem);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function (for internal use only) locates an element in an
|
||||
** hash table that matches the given key. The hash for this key has
|
||||
** already been computed and is passed as the 4th parameter.
|
||||
*/
|
||||
static HashElem *findElementGivenHash(
|
||||
const Hash *pH, /* The pH to be searched */
|
||||
const void *pKey, /* The key we are searching for */
|
||||
int nKey,
|
||||
int h /* The hash for this key. */
|
||||
){
|
||||
HashElem *elem; /* Used to loop thru the element list */
|
||||
int count; /* Number of elements left to test */
|
||||
|
||||
if( pH->ht ){
|
||||
struct _ht *pEntry = &pH->ht[h];
|
||||
elem = pEntry->chain;
|
||||
count = pEntry->count;
|
||||
while( count-- && elem ){
|
||||
if( binCompare(elem->pKey,elem->nKey,pKey,nKey)==0 ){
|
||||
return elem;
|
||||
}
|
||||
elem = elem->next;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove a single entry from the hash table given a pointer to that
|
||||
** element and a hash on the element's key.
|
||||
*/
|
||||
static void removeElementGivenHash(
|
||||
Hash *pH, /* The pH containing "elem" */
|
||||
HashElem* elem, /* The element to be removed from the pH */
|
||||
int h /* Hash value for the element */
|
||||
){
|
||||
struct _ht *pEntry;
|
||||
if( elem->prev ){
|
||||
elem->prev->next = elem->next;
|
||||
}else{
|
||||
pH->first = elem->next;
|
||||
}
|
||||
if( elem->next ){
|
||||
elem->next->prev = elem->prev;
|
||||
}
|
||||
pEntry = &pH->ht[h];
|
||||
if( pEntry->chain==elem ){
|
||||
pEntry->chain = elem->next;
|
||||
}
|
||||
pEntry->count--;
|
||||
if( pEntry->count<=0 ){
|
||||
pEntry->chain = 0;
|
||||
}
|
||||
if( pH->copyKey && elem->pKey ){
|
||||
pH->xFree(elem->pKey);
|
||||
}
|
||||
pH->xFree( elem );
|
||||
pH->count--;
|
||||
if( pH->count<=0 ){
|
||||
assert( pH->first==0 );
|
||||
assert( pH->count==0 );
|
||||
HashClear(pH);
|
||||
}
|
||||
}
|
||||
|
||||
/* Attempt to locate an element of the hash table pH with a key
|
||||
** that matches pKey,nKey. Return the data for this element if it is
|
||||
** found, or NULL if there is no match.
|
||||
*/
|
||||
void * HashFind(const Hash *pH, const void *pKey, int nKey){
|
||||
int h; /* A hash on key */
|
||||
HashElem *elem; /* The element that matches key */
|
||||
|
||||
if( pH==0 || pH->ht==0 ) return 0;
|
||||
h = binHash(pKey,nKey);
|
||||
assert( (pH->htsize & (pH->htsize-1))==0 );
|
||||
elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1));
|
||||
return elem ? elem->data : 0;
|
||||
}
|
||||
|
||||
/* Insert an element into the hash table pH. The key is pKey,nKey
|
||||
** and the data is "data".
|
||||
**
|
||||
** If no element exists with a matching key, then a new
|
||||
** element is created. A copy of the key is made if the copyKey
|
||||
** flag is set. NULL is returned.
|
||||
**
|
||||
** If another element already exists with the same key, then the
|
||||
** new data replaces the old data and the old data is returned.
|
||||
** The key is not copied in this instance. If a malloc fails, then
|
||||
** the new data is returned and the hash table is unchanged.
|
||||
**
|
||||
** If the "data" parameter to this function is NULL, then the
|
||||
** element corresponding to "key" is removed from the hash table.
|
||||
*/
|
||||
void * HashInsert(
|
||||
Hash *pH, /* The hash table to insert into */
|
||||
const void *pKey, /* The key */
|
||||
int nKey, /* Number of bytes in the key */
|
||||
void *data /* The data */
|
||||
){
|
||||
int hraw; /* Raw hash value of the key */
|
||||
int h; /* the hash of the key modulo hash table size */
|
||||
HashElem *elem; /* Used to loop thru the element list */
|
||||
HashElem *new_elem; /* New element added to the pH */
|
||||
|
||||
assert( pH!=0 );
|
||||
hraw = binHash(pKey, nKey);
|
||||
assert( (pH->htsize & (pH->htsize-1))==0 );
|
||||
h = hraw & (pH->htsize-1);
|
||||
elem = findElementGivenHash(pH,pKey,nKey,h);
|
||||
if( elem ){
|
||||
void *old_data = elem->data;
|
||||
if( data==0 ){
|
||||
removeElementGivenHash(pH,elem,h);
|
||||
}else{
|
||||
elem->data = data;
|
||||
}
|
||||
return old_data;
|
||||
}
|
||||
if( data==0 ) return 0;
|
||||
new_elem = (HashElem*)pH->xMalloc( sizeof(HashElem) );
|
||||
if( new_elem==0 ) return data;
|
||||
if( pH->copyKey && pKey!=0 ){
|
||||
new_elem->pKey = pH->xMalloc( nKey );
|
||||
if( new_elem->pKey==0 ){
|
||||
pH->xFree(new_elem);
|
||||
return data;
|
||||
}
|
||||
memcpy((void*)new_elem->pKey, pKey, nKey);
|
||||
}else{
|
||||
new_elem->pKey = (void*)pKey;
|
||||
}
|
||||
new_elem->nKey = nKey;
|
||||
pH->count++;
|
||||
if( pH->htsize==0 ){
|
||||
rehash(pH,8);
|
||||
if( pH->htsize==0 ){
|
||||
pH->count = 0;
|
||||
pH->xFree(new_elem);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
if( pH->count > pH->htsize ){
|
||||
rehash(pH,pH->htsize*2);
|
||||
}
|
||||
assert( pH->htsize>0 );
|
||||
assert( (pH->htsize & (pH->htsize-1))==0 );
|
||||
h = hraw & (pH->htsize-1);
|
||||
insertElement(pH, &pH->ht[h], new_elem);
|
||||
new_elem->data = data;
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
#ifndef _HASH_H_
|
||||
#define _HASH_H_
|
||||
|
||||
/* Forward declarations of structures. */
|
||||
typedef struct Hash Hash;
|
||||
typedef struct HashElem HashElem;
|
||||
|
||||
/* A complete hash table is an instance of the following structure.
|
||||
** The internals of this structure are intended to be opaque -- client
|
||||
** code should not attempt to access or modify the fields of this structure
|
||||
** directly. Change this structure only by using the routines below.
|
||||
** However, many of the "procedures" and "functions" for modifying and
|
||||
** accessing this structure are really macros, so we can't really make
|
||||
** this structure opaque.
|
||||
*/
|
||||
struct Hash {
|
||||
char copyKey; /* True if copy of key made on insert */
|
||||
int count; /* Number of entries in this table */
|
||||
HashElem *first; /* The first element of the array */
|
||||
void *(*xMalloc)(size_t); /* malloc() function to use */
|
||||
void (*xFree)(void *); /* free() function to use */
|
||||
int htsize; /* Number of buckets in the hash table */
|
||||
struct _ht { /* the hash table */
|
||||
int count; /* Number of entries with this hash */
|
||||
HashElem *chain; /* Pointer to first entry with this hash */
|
||||
} *ht;
|
||||
};
|
||||
|
||||
/* Each element in the hash table is an instance of the following
|
||||
** structure. All elements are stored on a single doubly-linked list.
|
||||
**
|
||||
** Again, this structure is intended to be opaque, but it can't really
|
||||
** be opaque because it is used by macros.
|
||||
*/
|
||||
struct HashElem {
|
||||
HashElem *next, *prev; /* Next and previous elements in the table */
|
||||
void *data; /* Data associated with this element */
|
||||
void *pKey; int nKey; /* Key associated with this element */
|
||||
};
|
||||
|
||||
/*
|
||||
** Access routines. To delete, insert a NULL pointer.
|
||||
*/
|
||||
Hash * HashCreate(char copyKey);
|
||||
Hash * HashCreateAlloc(char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *));
|
||||
|
||||
void HashFree(Hash*);
|
||||
|
||||
void HashInit(Hash*, char copyKey, void *(*xMalloc)(size_t), void (*xFree)(void *));
|
||||
void * HashInsert(Hash*, const void *pKey, int nKey, void *pData);
|
||||
void * HashFind(const Hash*, const void *pKey, int nKey);
|
||||
void HashClear(Hash*);
|
||||
|
||||
void * HashInsertSz(Hash*, const char *pKey, void *pData);
|
||||
void * HashFindSz(const Hash*, const char *pKey);
|
||||
|
||||
/*
|
||||
Element deletion macro
|
||||
*/
|
||||
#define HashDelete(H, K, N) (HashInsert(H, K, N, 0))
|
||||
|
||||
/*
|
||||
** Macros for looping over all elements of a hash table. The idiom is
|
||||
** like this:
|
||||
**
|
||||
** Hash h;
|
||||
** HashElem *p;
|
||||
** ...
|
||||
** for(p=HashFirst(&h); p; p=HashNext(p)){
|
||||
** SomeStructure *pData = HashData(p);
|
||||
** // do something with pData
|
||||
** }
|
||||
*/
|
||||
#define HashFirst(H) ((H)->first)
|
||||
#define HashNext(E) ((E)->next)
|
||||
#define HashData(E) ((E)->data)
|
||||
#define HashKey(E) ((E)->pKey)
|
||||
#define HashKeysize(E) ((E)->nKey)
|
||||
|
||||
/*
|
||||
** Number of entries in a hash table
|
||||
*/
|
||||
#define HashCount(H) ((H)->count)
|
||||
|
||||
|
||||
/*
|
||||
more macros
|
||||
*/
|
||||
typedef struct Hash* hash_table;
|
||||
typedef struct HashElem* hash_elem;
|
||||
|
||||
#define hash_create() (HashCreate(1))
|
||||
#define hash_insert(H, K, V) (HashInsertSz(H, K, V))
|
||||
#define hash_find(H, K) (HashFindSz(H, K))
|
||||
#define hash_delete(H, K) (HashInsertSz(H, K, 0))
|
||||
#define hash_clear(H) (HashClear(H))
|
||||
#define hash_free(H) (HashFree(H))
|
||||
|
||||
#define hash_first(H) ((H)->first)
|
||||
#define hash_next(E) ((E)->next)
|
||||
#define hash_data(E) ((E)->data)
|
||||
#define hash_key(E) ((E)->pKey)
|
||||
#define hash_keysize(E) ((E)->nKey)
|
||||
|
||||
#define hash_count(H) ((H)->count)
|
||||
|
||||
#endif /* _HASH_H_ */
|
|
@ -0,0 +1,44 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "io.h"
|
||||
|
||||
/* callback function to mimic fread using a memory buffer */
|
||||
size_t buffer_read(void * out_buffer, size_t size, void * user_data) {
|
||||
buffer_context * ctxt = (buffer_context *)user_data;
|
||||
if (ctxt->current_address >= ctxt->start_address &&
|
||||
ctxt->current_address + size <= ctxt->start_address + ctxt->buffer_size) {
|
||||
|
||||
memcpy(out_buffer, ctxt->current_address, size);
|
||||
ctxt->current_address += size;
|
||||
return size;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* callback function to mimic fwrite using a memory buffer */
|
||||
size_t buffer_write(const void * in_buffer, size_t size, void * user_data) {
|
||||
buffer_context * ctxt = (buffer_context *)user_data;
|
||||
if (ctxt->current_address >= ctxt->start_address &&
|
||||
ctxt->current_address + size <= ctxt->start_address + ctxt->buffer_size) {
|
||||
|
||||
memcpy(ctxt->current_address, in_buffer, size);
|
||||
ctxt->current_address += size;
|
||||
return size;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* callback function to read data from a file stream */
|
||||
size_t file_read(void * out_buffer, size_t size, void * user_data) {
|
||||
return fread(out_buffer, sizeof(uint8_t), size, (FILE *)user_data);
|
||||
}
|
||||
|
||||
/* callback function to write data to a file stream */
|
||||
size_t file_write(const void * in_buffer, size_t size, void * user_data) {
|
||||
return fwrite(in_buffer, sizeof(uint8_t), size, (FILE *)user_data);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef __IO_H__
|
||||
#define __IO_H__
|
||||
|
||||
#include "amf.h"
|
||||
|
||||
/* structure used to mimic a stream with a memory buffer */
|
||||
typedef struct __buffer_context {
|
||||
uint8_t * start_address;
|
||||
uint8_t * current_address;
|
||||
size_t buffer_size;
|
||||
} buffer_context;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* callback function to mimic fread using a memory buffer */
|
||||
size_t buffer_read(void * out_buffer, size_t size, void * user_data);
|
||||
|
||||
/* callback function to mimic fwrite using a memory buffer */
|
||||
size_t buffer_write(const void * in_buffer, size_t size, void * user_data);
|
||||
|
||||
/* callback function to read data from a file stream */
|
||||
size_t file_read(void * out_buffer, size_t size, void * user_data);
|
||||
|
||||
/* callback function to write data to a file stream */
|
||||
size_t file_write(const void * in_buffer, size_t size, void * user_data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __IO_H__ */
|
|
@ -0,0 +1,172 @@
|
|||
#include "ptrarray.h"
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
Customize default capacity
|
||||
*/
|
||||
#ifndef PTRARRAY_DEFAULT_CAPACITY
|
||||
# define PTRARRAY_DEFAULT_CAPACITY 5
|
||||
#endif
|
||||
|
||||
/**
|
||||
Enable array security checking
|
||||
*/
|
||||
/* #define PTRARRAY_SECURITY_CHECKS */
|
||||
|
||||
/**
|
||||
Customize memory allocation routines
|
||||
*/
|
||||
#ifndef PTRARRAY_MALLOC
|
||||
# define PTRARRAY_MALLOC malloc
|
||||
#endif
|
||||
|
||||
#ifndef PTRARRAY_FREE
|
||||
# define PTRARRAY_FREE free
|
||||
#endif
|
||||
|
||||
#ifndef PTRARRAY_REALLOC
|
||||
# define PTRARRAY_REALLOC realloc
|
||||
#endif
|
||||
|
||||
/**
|
||||
This function doubles the current capacity
|
||||
of the given dynamic array.
|
||||
*/
|
||||
static int ptrarray_grow(ptrarray * array) {
|
||||
void * new_mem;
|
||||
size_t new_capacity;
|
||||
#ifdef PTRARRAY_SECURITY_CHECKS
|
||||
if (array == NULL)
|
||||
return;
|
||||
#endif
|
||||
new_capacity = array->capacity * 2;
|
||||
new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*));
|
||||
if (new_mem != NULL) {
|
||||
array->data = new_mem;
|
||||
array->capacity = new_capacity;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ptrarray_init(ptrarray * array, size_t initial_capacity, data_free_proc free_proc) {
|
||||
if (free_proc == NULL) {
|
||||
free_proc = PTRARRAY_FREE;
|
||||
}
|
||||
array->data_free = free_proc;
|
||||
if (initial_capacity <= 0) {
|
||||
initial_capacity = PTRARRAY_DEFAULT_CAPACITY;
|
||||
}
|
||||
array->capacity = initial_capacity;
|
||||
array->data = PTRARRAY_MALLOC(initial_capacity * sizeof(void*));
|
||||
array->size = 0;
|
||||
}
|
||||
|
||||
/*size_t ptrarray_capacity(ptrarray * array) {
|
||||
return array->capacity;
|
||||
}*/
|
||||
|
||||
/*size_t ptrarray_size(ptrarray * array) {
|
||||
return array->size;
|
||||
}*/
|
||||
|
||||
/*int ptrarray_empty(ptrarray * array) {
|
||||
return !((array)->size);
|
||||
}*/
|
||||
|
||||
void ptrarray_reserve(ptrarray * array, size_t new_capacity) {
|
||||
void * new_mem;
|
||||
#ifdef PTRARRAY_SECURITY_CHECKS
|
||||
if (array == NULL)
|
||||
return;
|
||||
#endif
|
||||
if (new_capacity > array->capacity) {
|
||||
new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*));
|
||||
if (new_mem != NULL) {
|
||||
array->data = new_mem;
|
||||
array->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
else if (new_capacity < array->capacity) {
|
||||
new_capacity = (new_capacity < array->size) ? array->size : new_capacity;
|
||||
new_capacity = (new_capacity < PTRARRAY_DEFAULT_CAPACITY) ? PTRARRAY_DEFAULT_CAPACITY : new_capacity;
|
||||
new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*));
|
||||
if (new_mem != NULL) {
|
||||
array->data = new_mem;
|
||||
array->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ptrarray_compact(ptrarray * array) {
|
||||
size_t new_capacity;
|
||||
void * new_mem;
|
||||
#ifdef PTRARRAY_SECURITY_CHECKS
|
||||
if (array == NULL)
|
||||
return;
|
||||
#endif
|
||||
new_capacity = (array->size < PTRARRAY_DEFAULT_CAPACITY) ? PTRARRAY_DEFAULT_CAPACITY : array->size;
|
||||
new_mem = PTRARRAY_REALLOC(array->data, new_capacity * sizeof(void*));
|
||||
if (new_mem != NULL) {
|
||||
array->data = new_mem;
|
||||
array->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
void ptrarray_push(ptrarray * array, void * data) {
|
||||
#ifdef PTRARRAY_SECURITY_CHECKS
|
||||
if (array == NULL)
|
||||
return;
|
||||
#endif
|
||||
if (array->size == array->capacity) {
|
||||
if (!ptrarray_grow(array)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
array->data[array->size++] = data;
|
||||
}
|
||||
|
||||
void * ptrarray_pop(ptrarray * array) {
|
||||
#ifdef PTRARRAY_SECURITY_CHECKS
|
||||
if (array == NULL)
|
||||
return NULL;
|
||||
#endif
|
||||
if (ptrarray_empty(array)) {
|
||||
return NULL;
|
||||
}
|
||||
return array->data[array->size--];
|
||||
}
|
||||
|
||||
void ptrarray_insert(ptrarray * array, size_t position, void * data) {
|
||||
void ** src_pos;
|
||||
|
||||
#ifdef PTRARRAY_SECURITY_CHECKS
|
||||
if (array == NULL)
|
||||
return;
|
||||
#endif
|
||||
if (array->size > position) {
|
||||
if (array->size == array->capacity) {
|
||||
if (!ptrarray_grow(array)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
src_pos = array->data + position;
|
||||
memmove(src_pos + 1, src_pos, array->size - position);
|
||||
*src_pos = data;
|
||||
}
|
||||
}
|
||||
|
||||
void ptrarray_prepend(ptrarray * array, void * data);
|
||||
void * ptrarray_replace(ptrarray * array, size_t position, void * data);
|
||||
|
||||
void * ptrarray_remove(ptrarray * array, size_t position);
|
||||
void ptrarray_clear(ptrarray * array);
|
||||
|
||||
void * ptrarray_first(ptrarray * array);
|
||||
void * ptrarray_last(ptrarray * array);
|
||||
void * ptrarray_get(size_t position);
|
||||
|
||||
void ptrarray_destroy(ptrarray * array) {
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef __PTRARRAY_H__
|
||||
#define __PTRARRAY_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void (*data_free_proc)(void *);
|
||||
|
||||
typedef struct __ptrarray {
|
||||
size_t capacity;
|
||||
size_t size;
|
||||
void ** data;
|
||||
data_free_proc data_free;
|
||||
} ptrarray;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void ptrarray_init(ptrarray * array, size_t initial_capacity, data_free_proc free_proc);
|
||||
|
||||
/*size_t ptrarray_capacity(ptrarray * array);*/
|
||||
#define ptrarray_capacity(a) ((a)->capacity)
|
||||
|
||||
/*size_t ptrarray_size(ptrarray * array);*/
|
||||
#define ptrarray_size(a) ((a)->size)
|
||||
|
||||
/*int ptrarray_empty(ptrarray * array);*/
|
||||
#define ptrarray_empty(a) (!((a)->size))
|
||||
|
||||
void ptrarray_reserve(ptrarray * array, size_t new_capacity);
|
||||
void ptrarray_compact(ptrarray * array);
|
||||
|
||||
void ptrarray_push(ptrarray * array, void * data);
|
||||
void * ptrarray_pop(ptrarray * array);
|
||||
void ptrarray_insert(ptrarray * array, size_t position, void * data);
|
||||
void ptrarray_prepend(ptrarray * array, void * data);
|
||||
void * ptrarray_replace(ptrarray * array, size_t position, void * data);
|
||||
|
||||
void * ptrarray_remove(ptrarray * array, size_t position);
|
||||
void ptrarray_clear(ptrarray * array);
|
||||
|
||||
void * ptrarray_first(ptrarray * array);
|
||||
void * ptrarray_last(ptrarray * array);
|
||||
void * ptrarray_get(size_t position);
|
||||
|
||||
void ptrarray_destroy(ptrarray * array);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __PTRARRAY_H__ */
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
$Id: types.c 1 2009-11-13 00:04:24Z noirotm $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007, 2008 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta 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.
|
||||
|
||||
FLVMeta 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 FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#ifndef WORDS_BIGENDIAN
|
||||
|
||||
/* swap 64 bits doubles */
|
||||
typedef union __convert_u {
|
||||
uint64_t i;
|
||||
number64_t f;
|
||||
} convert_u;
|
||||
|
||||
number64_t swap_number64(number64_t n) {
|
||||
convert_u c;
|
||||
c.f = n;
|
||||
c.i = (((c.i & 0x00000000000000FFULL) << 56) |
|
||||
((c.i & 0x000000000000FF00ULL) << 40) |
|
||||
((c.i & 0x0000000000FF0000ULL) << 24) |
|
||||
((c.i & 0x00000000FF000000ULL) << 8) |
|
||||
((c.i & 0x000000FF00000000ULL) >> 8) |
|
||||
((c.i & 0x0000FF0000000000ULL) >> 24) |
|
||||
((c.i & 0x00FF000000000000ULL) >> 40) |
|
||||
((c.i & 0xFF00000000000000ULL) >> 56));
|
||||
return c.f;
|
||||
}
|
||||
#endif /* !WORDS_BIGENDIAN */
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
$Id: types.h 1 2009-11-13 00:04:24Z noirotm $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007, 2008 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta 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.
|
||||
|
||||
FLVMeta 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 FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __TYPES_H__
|
||||
#define __TYPES_H__
|
||||
|
||||
#include "amf.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
|
||||
# define swap_uint16(x) (x)
|
||||
# define swap_int16(x) (x)
|
||||
# define swap_uint32(x) (x)
|
||||
# define swap_number64(x) (x)
|
||||
|
||||
#else /* WORDS_BIGENDIAN */
|
||||
/* swap 16 bits integers */
|
||||
# define swap_uint16(x) ((((x) & 0x00FFU) << 8) | (((x) & 0xFF00U) >> 8))
|
||||
# define swap_sint16(x) ((((x) & 0x00FF) << 8) | (((x) & 0xFF00) >> 8))
|
||||
|
||||
/* swap 32 bits integers */
|
||||
# define swap_uint32(x) ((((x) & 0x000000FFU) << 24) | \
|
||||
(((x) & 0x0000FF00U) << 8) | \
|
||||
(((x) & 0x00FF0000U) >> 8) | \
|
||||
(((x) & 0xFF000000U) >> 24))
|
||||
|
||||
/* swap 64 bits doubles */
|
||||
number64_t swap_number64(number64_t);
|
||||
|
||||
#endif /* WORDS_BIGENDIAN */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __TYPES_H__ */
|
|
@ -0,0 +1,5 @@
|
|||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
|
||||
add_executable(amf0_demo amf0_demo.c)
|
||||
add_dependencies(amf0_demo amf)
|
||||
|
Binary file not shown.
|
@ -0,0 +1,17 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include "amf0.h"
|
||||
#include "io.h"
|
||||
#include "types.h"
|
||||
|
||||
int main() {
|
||||
amf0_data * test;
|
||||
|
||||
test = amf0_object_new();
|
||||
amf0_object_add(test, "toto", amf0_str("une chaine de caracteres"));
|
||||
amf0_object_add(test, "test_bool", amf0_boolean_new(1));
|
||||
|
||||
amf0_data_dump(stdout, test, 0);
|
||||
|
||||
amf0_data_free(test);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,638 @@
|
|||
/*
|
||||
* mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2011, Barracuda Networks Inc.
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is Barracuda Networks Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Mathieu Rene <mrene@avgs.ca>
|
||||
*
|
||||
* mod_rtmp.h -- RTMP Endpoint Module
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MOD_RTMP_H
|
||||
#define MOD_RTMP_H
|
||||
#include <switch.h>
|
||||
|
||||
/* AMF */
|
||||
#include "amf0.h"
|
||||
#include "io.h"
|
||||
#include "types.h"
|
||||
|
||||
//#define RTMP_DEBUG_IO
|
||||
#define RTMP_DONT_HOLD
|
||||
|
||||
#define RTMP_THREE_WAY_UUID_VARIABLE "rtmp_three_way_uuid"
|
||||
#define RTMP_ATTACH_ON_HANGUP_VARIABLE "rtmp_attach_on_hangup"
|
||||
#define RTMP_USER_VARIABLE_PREFIX "rtmp_u_"
|
||||
|
||||
#define RTMP_DEFAULT_PORT 1935
|
||||
#define RTMP_TCP_READ_BUF 2048
|
||||
#define AMF_MAX_SIZE 2048
|
||||
|
||||
#define SUPPORT_SND_NONE 0x0000
|
||||
#define SUPPORT_SND_ADPCM 0x0002
|
||||
#define SUPPORT_SND_MP3 0x0004
|
||||
#define SUPPORT_SND_INTEL 0x0008
|
||||
#define SUPPORT_SND_UNUSED 0x0010
|
||||
#define SUPPORT_SND_NELLY8 0x0020
|
||||
#define SUPPORT_SND_NELLY 0x0040
|
||||
#define SUPPORT_SND_G711A 0x0080
|
||||
#define SUPPORT_SND_G711U 0x0100
|
||||
#define SUPPORT_SND_NELLY16 0x0200
|
||||
#define SUPPORT_SND_AAC 0x0400
|
||||
#define SUPPORT_SND_SPEEX 0x0800
|
||||
#define SUPPORT_SND_ALL 0x0FFF
|
||||
|
||||
#define SUPPORT_VID_UNUSED 0x0001
|
||||
#define SUPPORT_VID_JPEG 0x0002
|
||||
#define SUPPORT_VID_SORENSON 0x0004
|
||||
#define SUPPORT_VID_HOMEBREW 0x0008
|
||||
#define SUPPORT_VID_VP6 0x0010
|
||||
#define SUPPORT_VID_VP6ALPHA 0x0020
|
||||
#define SUPPORT_VID_HOMEBREWV 0x0040
|
||||
#define SUPPORT_VID_H264 0x0080
|
||||
#define SUPPORT_VID_ALL 0x00FF
|
||||
|
||||
#define SUPPORT_VID_CLIENT_SEEK 1
|
||||
|
||||
#define kAMF0 0
|
||||
#define kAMF3 3
|
||||
|
||||
#define RTMP_DEFAULT_ACK_WINDOW 0x20000
|
||||
|
||||
#define RTMP_TYPE_CHUNKSIZE 0x01
|
||||
#define RTMP_TYPE_ABORT 0x2
|
||||
#define RTMP_TYPE_ACK 0x3
|
||||
#define RTMP_TYPE_USERCTRL 0x04
|
||||
#define RTMP_TYPE_WINDOW_ACK_SIZE 0x5
|
||||
#define RTMP_TYPE_SET_PEER_BW 0x6
|
||||
#define RTMP_TYPE_AUDIO 0x08
|
||||
#define RTMP_TYPE_VIDEO 0x09
|
||||
#define RTMP_TYPE_METADATA 0x12
|
||||
#define RTMP_TYPE_INVOKE 0x14
|
||||
#define RTMP_TYPE_NOTIFY 0x12
|
||||
|
||||
#define RTMP_CTRL_STREAM_BEGIN 0x00
|
||||
#define RTMP_CTRL_STREAM_EOF 0x01
|
||||
#define RTMP_CTRL_STREAM_DRY 0x02
|
||||
#define RTMP_CTRL_SET_BUFFER_LENGTH 0x03
|
||||
#define RTMP_CTRL_STREAM_IS_RECORDED 0x04
|
||||
#define RTMP_CTRL_PING_REQUEST 0x06
|
||||
#define RTMP_CTRL_PING_RESPONSE 0x07
|
||||
|
||||
#define RTMP_DEFAULT_STREAM_CONTROL 0x02
|
||||
#define RTMP_DEFAULT_STREAM_INVOKE 0x03
|
||||
#define RTMP_DEFAULT_STREAM_NOTIFY 0x05
|
||||
#define RTMP_DEFAULT_STREAM_VIDEO 0x07
|
||||
#define RTMP_DEFAULT_STREAM_AUDIO 0x06
|
||||
|
||||
|
||||
#define RTMP_MSGSTREAM_DEFAULT 0x0
|
||||
/* It seems everything media-related (play/onStatus and the actual audio data are using this stream) */
|
||||
#define RTMP_MSGSTREAM_MEDIA 0x01
|
||||
|
||||
#define RTMP_EVENT_CONNECT "rtmp::connect"
|
||||
#define RTMP_EVENT_DISCONNECT "rtmp::disconnect"
|
||||
#define RTMP_EVENT_REGISTER "rtmp::register"
|
||||
#define RTMP_EVENT_UNREGISTER "rtmp::unregister"
|
||||
#define RTMP_EVENT_LOGIN "rtmp::login"
|
||||
#define RTMP_EVENT_LOGOUT "rtmp::logout"
|
||||
#define RTMP_EVENT_ATTACH "rtmp::attach"
|
||||
#define RTMP_EVENT_DETACH "rtmp::detach"
|
||||
#define RTMP_EVENT_CUSTOM "rtmp::custom"
|
||||
#define RTMP_EVENT_CLIENTCUSTOM "rtmp::clientcustom"
|
||||
|
||||
#define INT32_LE(x) (x) & 0xFF, ((x) >> 8) & 0xFF, ((x) >> 16) & 0xFF, ((x) >> 24) & 0xFF
|
||||
#define INT32(x) ((x) >> 24) & 0xFF, ((x) >> 16) & 0xFF, ((x) >> 8) & 0xFF, (x) & 0xFF
|
||||
#define INT24(x) ((x) >> 16) & 0xFF, ((x) >> 8) & 0xFF, (x) & 0xFF
|
||||
#define INT16(x) ((x) >> 8) & 0xFF, (x) & 0xFF
|
||||
|
||||
|
||||
typedef enum {
|
||||
RTMP_AUDIO_PCM = 0,
|
||||
RTMP_AUDIO_ADPCM = 1,
|
||||
RTMP_AUDIO_MP3 = 2,
|
||||
RTMP_AUDIO_NELLYMOSER_8K_MONO= 5,
|
||||
RTMP_AUDIO_NELLYMOSER = 6,
|
||||
RTMP_AUDIO_SPEEX = 11
|
||||
} rtmp_audio_format_t;
|
||||
|
||||
/*
|
||||
|
||||
From: http://osflash.org/flv
|
||||
|
||||
0x08: AUDIO
|
||||
The first byte of an audio packet contains bitflags that describe the codec used, with the following layout:
|
||||
|
||||
|
||||
|
||||
Name Expression Description
|
||||
soundType (byte & 0×01) » 0 0: mono, 1: stereo
|
||||
soundSize (byte & 0×02) » 1 0: 8-bit, 1: 16-bit
|
||||
soundRate (byte & 0x0C) » 2 0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz
|
||||
soundFormat (byte & 0xf0) » 4 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser, 11: Speex
|
||||
|
||||
0x09: VIDEO
|
||||
The first byte of a video packet describes contains bitflags that describe the codec used, and the type of frame
|
||||
|
||||
Name Expression Description
|
||||
codecID (byte & 0x0f) » 0 2: Sorensen H.263, 3: Screen video, 4: On2 VP6, 5: On2 VP6 Alpha, 6: ScreenVideo 2
|
||||
frameType (byte & 0xf0) » 4 1: keyframe, 2: inter frame, 3: disposable inter frame
|
||||
|
||||
0x12: META
|
||||
The contents of a meta packet are two AMF packets.
|
||||
The first is almost always a short uint16_be length-prefixed UTF-8 string (AMF type 0×02),
|
||||
and the second is typically a mixed array (AMF type 0×08). However, the second chunk typically contains a variety of types,
|
||||
so a full AMF parser should be used.
|
||||
*/
|
||||
|
||||
|
||||
static inline int rtmp_audio_codec_get_channels(uint8_t codec) {
|
||||
return (codec & 0x01) ? 2 : 1;
|
||||
}
|
||||
|
||||
static inline int rtmp_audio_codec_get_sample_size(uint8_t codec) {
|
||||
return (codec & 0x02) ? 16 : 8;
|
||||
}
|
||||
|
||||
static inline int rtmp_audio_codec_get_rate(uint8_t codec) {
|
||||
switch(codec & 0x0C) {
|
||||
case 0:
|
||||
return 5500;
|
||||
case 1:
|
||||
return 11000;
|
||||
case 2:
|
||||
return 22000;
|
||||
case 3:
|
||||
return 44000;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline rtmp_audio_format_t rtmp_audio_codec_get_format(uint8_t codec) {
|
||||
return (rtmp_audio_format_t)(codec & 0xf0);
|
||||
}
|
||||
|
||||
static inline uint8_t rtmp_audio_codec(int channels, int bits, int rate, rtmp_audio_format_t format) {
|
||||
uint8_t codec = 0;
|
||||
|
||||
switch (channels) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
codec |= 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (bits) {
|
||||
case 8:
|
||||
break;
|
||||
case 16:
|
||||
codec |= 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (rate) {
|
||||
case 0:
|
||||
case 5500:
|
||||
break;
|
||||
case 11000:
|
||||
codec |= 0x4;
|
||||
break;
|
||||
case 22000:
|
||||
codec |= 0x8;
|
||||
break;
|
||||
case 44000:
|
||||
codec |= 0xC;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(format) {
|
||||
case RTMP_AUDIO_PCM:
|
||||
break;
|
||||
case RTMP_AUDIO_ADPCM:
|
||||
codec |= 0x10;
|
||||
break;
|
||||
case RTMP_AUDIO_MP3:
|
||||
codec |= 0x20;
|
||||
break;
|
||||
case RTMP_AUDIO_NELLYMOSER_8K_MONO:
|
||||
codec |= 0x50;
|
||||
break;
|
||||
case RTMP_AUDIO_NELLYMOSER:
|
||||
codec |= 0x60;
|
||||
break;
|
||||
case RTMP_AUDIO_SPEEX:
|
||||
codec |= 0x80;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return codec;
|
||||
}
|
||||
|
||||
|
||||
struct rtmp_session;
|
||||
typedef struct rtmp_session rtmp_session_t;
|
||||
|
||||
struct rtmp_profile;
|
||||
typedef struct rtmp_profile rtmp_profile_t;
|
||||
|
||||
typedef struct rtmp_state rtmp_state_t;
|
||||
|
||||
#define RTMP_INVOKE_FUNCTION_ARGS rtmp_session_t *rsession, rtmp_state_t *state, int amfnumber, int transaction_id, int argc, amf0_data *argv[]
|
||||
|
||||
typedef switch_status_t (*rtmp_invoke_function_t)(RTMP_INVOKE_FUNCTION_ARGS);
|
||||
|
||||
#define RTMP_INVOKE_FUNCTION(_x) switch_status_t _x (RTMP_INVOKE_FUNCTION_ARGS)
|
||||
|
||||
/* AMF Helpers */
|
||||
|
||||
#define amf0_is_string(_x) (_x && (_x)->type == AMF0_TYPE_STRING)
|
||||
#define amf0_is_number(_x) (_x && (_x)->type == AMF0_TYPE_NUMBER)
|
||||
#define amf0_is_boolean(_x) (_x && (_x)->type == AMF0_TYPE_BOOLEAN)
|
||||
#define amf0_is_object(_x) (_x && (_x)->type == AMF0_TYPE_OBJECT)
|
||||
|
||||
static inline char *amf0_get_string(amf0_data *x)
|
||||
{
|
||||
return (amf0_is_string(x) ? (char*)amf0_string_get_uint8_ts(x) : NULL);
|
||||
}
|
||||
|
||||
static inline int amf0_get_number(amf0_data *x)
|
||||
{
|
||||
return (amf0_is_number(x) ? amf0_number_get_value(x) : 0);
|
||||
}
|
||||
|
||||
static inline switch_bool_t amf0_get_boolean(amf0_data *x)
|
||||
{
|
||||
return (amf0_is_boolean(x) ? amf0_boolean_get_value(x) : SWITCH_FALSE);
|
||||
}
|
||||
|
||||
struct rtmp_io {
|
||||
switch_status_t (*read)(rtmp_session_t *rsession, unsigned char *buf, switch_size_t *len);
|
||||
switch_status_t (*write)(rtmp_session_t *rsession, const unsigned char *buf, switch_size_t *len);
|
||||
switch_status_t (*close)(rtmp_session_t *rsession);
|
||||
rtmp_profile_t *profile;
|
||||
switch_memory_pool_t *pool;
|
||||
int running;
|
||||
const char *name;
|
||||
const char *address;
|
||||
};
|
||||
|
||||
typedef struct rtmp_io rtmp_io_t;
|
||||
|
||||
typedef enum {
|
||||
TFLAG_IO = (1 << 0),
|
||||
TFLAG_DETACHED = (1 << 1), /* Call isn't the current active call */
|
||||
TFLAG_BREAK = (1 << 2),
|
||||
TFLAG_THREE_WAY = (1 << 3), /* In a three-way call */
|
||||
TFLAG_VID_WAIT_KEYFRAME = (1 << 4) /* Wait for video keyframe */
|
||||
} TFLAGS;
|
||||
|
||||
|
||||
/* Session flags */
|
||||
typedef enum {
|
||||
SFLAG_AUDIO = (1 << 0), /* < Send audio */
|
||||
SFLAG_VIDEO = (1 << 1) /* < Send video */
|
||||
} SFLAGS;
|
||||
|
||||
typedef enum {
|
||||
PFLAG_RUNNING = (1 << 0)
|
||||
} PFLAGS;
|
||||
|
||||
struct mod_rtmp_globals {
|
||||
switch_endpoint_interface_t *rtmp_endpoint_interface;
|
||||
switch_memory_pool_t *pool;
|
||||
switch_mutex_t *mutex;
|
||||
switch_hash_t *profile_hash;
|
||||
switch_thread_rwlock_t *profile_rwlock;
|
||||
switch_hash_t *session_hash;
|
||||
switch_thread_rwlock_t *session_rwlock;
|
||||
switch_hash_t *invoke_hash;
|
||||
};
|
||||
|
||||
extern struct mod_rtmp_globals rtmp_globals;
|
||||
|
||||
struct rtmp_profile {
|
||||
char *name; /* < Profile name */
|
||||
switch_memory_pool_t *pool; /* < Memory pool */
|
||||
rtmp_io_t *io; /* < IO Module instance */
|
||||
switch_thread_rwlock_t *rwlock; /* < Rwlock for reference counting */
|
||||
uint32_t flags; /* < PFLAGS */
|
||||
switch_mutex_t *mutex; /* < Mutex for call count */
|
||||
int calls; /* < Active calls count */
|
||||
int clients; /* < Number of connected clients */
|
||||
switch_hash_t *session_hash; /* < Active rtmp sessions */
|
||||
switch_thread_rwlock_t *session_rwlock; /* < rwlock for session hashtable */
|
||||
const char *context; /* < Default dialplan name */
|
||||
const char *dialplan; /* < Default dialplan context */
|
||||
const char *bind_address; /* < Bind address */
|
||||
const char *io_name; /* < Name of I/O module (from config) */
|
||||
int chunksize; /* < Override default chunksize (from config) */
|
||||
int buffer_len; /* < Receive buffer length the flash clients should use */
|
||||
|
||||
switch_hash_t *reg_hash; /* < Registration hashtable */
|
||||
switch_thread_rwlock_t *reg_rwlock; /* < Registration hash rwlock */
|
||||
|
||||
switch_bool_t auth_calls; /* < Require authentiation */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
unsigned ts:24;
|
||||
unsigned len:24;
|
||||
unsigned type:8;
|
||||
unsigned src:16;
|
||||
unsigned dst:16;
|
||||
} rtmp_hdr_t;
|
||||
|
||||
#define RTMP_DEFAULT_CHUNKSIZE 128
|
||||
|
||||
struct rtmp_state {
|
||||
union {
|
||||
char sz[12];
|
||||
rtmp_hdr_t packed;
|
||||
} header;
|
||||
int remainlen;
|
||||
int origlen;
|
||||
|
||||
uint32_t ts; /* 24 bits max */
|
||||
uint32_t ts_delta; /* 24 bits max */
|
||||
uint8_t type;
|
||||
uint32_t stream_id;
|
||||
unsigned char buf[AMF_MAX_SIZE];
|
||||
switch_size_t buf_pos;
|
||||
};
|
||||
|
||||
|
||||
typedef enum {
|
||||
RS_HANDSHAKE = 0,
|
||||
RS_HANDSHAKE2 = 1,
|
||||
RS_ESTABLISHED = 2,
|
||||
RS_DESTROY = 3
|
||||
} rtmp_session_state_t;
|
||||
|
||||
struct rtmp_private;
|
||||
typedef struct rtmp_private rtmp_private_t;
|
||||
|
||||
|
||||
struct rtmp_account;
|
||||
typedef struct rtmp_account rtmp_account_t;
|
||||
|
||||
struct rtmp_account {
|
||||
const char *user;
|
||||
const char *domain;
|
||||
rtmp_account_t *next;
|
||||
};
|
||||
|
||||
struct rtmp_session {
|
||||
switch_memory_pool_t *pool;
|
||||
rtmp_profile_t *profile;
|
||||
char uuid[SWITCH_UUID_FORMATTED_LENGTH+1];
|
||||
void *io_private;
|
||||
|
||||
rtmp_session_state_t state;
|
||||
int parse_state;
|
||||
uint16_t parse_remain; /* < Remaining bytes required before changing parse state */
|
||||
|
||||
int hdrsize; /* < The current header size */
|
||||
int amfnumber; /* < The current AMF number */
|
||||
|
||||
rtmp_state_t amfstate[64];
|
||||
rtmp_state_t amfstate_out[64];
|
||||
|
||||
switch_mutex_t *socket_mutex;
|
||||
switch_mutex_t *count_mutex;
|
||||
int active_sessions;
|
||||
|
||||
unsigned char hsbuf[2048];
|
||||
int hspos;
|
||||
uint16_t in_chunksize;
|
||||
uint16_t out_chunksize;
|
||||
|
||||
/* Connect params */
|
||||
const char *flashVer;
|
||||
const char *swfUrl;
|
||||
const char *tcUrl;
|
||||
const char *app;
|
||||
const char *pageUrl;
|
||||
|
||||
uint32_t capabilities;
|
||||
uint32_t audioCodecs;
|
||||
uint32_t videoCodecs;
|
||||
uint32_t videoFunction;
|
||||
|
||||
switch_thread_rwlock_t *rwlock;
|
||||
|
||||
rtmp_private_t *tech_pvt; /* < Active call's tech_pvt */
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
FILE *io_debug_in;
|
||||
FILE *io_debug_out;
|
||||
#endif
|
||||
|
||||
const char *remote_address;
|
||||
switch_port_t remote_port;
|
||||
|
||||
switch_hash_t *session_hash; /* < Hash of call uuids and tech_pvt */
|
||||
switch_thread_rwlock_t *session_rwlock; /* < RWLock protecting session_hash */
|
||||
|
||||
rtmp_account_t *account;
|
||||
switch_thread_rwlock_t *account_rwlock;
|
||||
uint_least32_t flags;
|
||||
|
||||
int8_t sendAudio, sendVideo;
|
||||
uint64_t recv_ack_window; /* < ACK Window */
|
||||
uint64_t recv_ack_sent; /* < Bytes ack'd */
|
||||
uint64_t recv; /* < Bytes received */
|
||||
|
||||
uint32_t send_ack_window;
|
||||
uint32_t send_ack;
|
||||
uint32_t send;
|
||||
switch_time_t send_ack_ts;
|
||||
|
||||
uint32_t send_bw; /* < Current send bandwidth (in bytes/sec) */
|
||||
|
||||
uint32_t next_streamid; /* < The next stream id that will be used */
|
||||
uint32_t active_streamid; /* < The stream id returned by the last call to createStream */
|
||||
|
||||
uint32_t media_streamid; /* < The stream id that was used for the last "play" command,
|
||||
where we should send media */
|
||||
};
|
||||
|
||||
struct rtmp_private {
|
||||
unsigned int flags;
|
||||
switch_codec_t read_codec;
|
||||
switch_codec_t write_codec;
|
||||
|
||||
switch_frame_t read_frame;
|
||||
unsigned char databuf[SWITCH_RECOMMENDED_BUFFER_SIZE]; /* < Buffer for read_frame */
|
||||
|
||||
switch_caller_profile_t *caller_profile;
|
||||
|
||||
switch_mutex_t *mutex;
|
||||
switch_mutex_t *flag_mutex;
|
||||
|
||||
switch_core_session_t *session;
|
||||
switch_channel_t *channel;
|
||||
rtmp_session_t *rtmp_session;
|
||||
|
||||
int read_channel; /* RTMP channel #s for read and write */
|
||||
int write_channel;
|
||||
uint8_t audio_codec;
|
||||
uint8_t video_codec;
|
||||
|
||||
switch_time_t stream_start_ts;
|
||||
switch_timer_t timer;
|
||||
switch_buffer_t *readbuf;
|
||||
switch_mutex_t *readbuf_mutex;
|
||||
|
||||
const char *display_callee_id_name;
|
||||
const char *display_callee_id_number;
|
||||
|
||||
const char *auth_user;
|
||||
const char *auth_domain;
|
||||
const char *auth;
|
||||
};
|
||||
|
||||
struct rtmp_reg;
|
||||
typedef struct rtmp_reg rtmp_reg_t;
|
||||
|
||||
struct rtmp_reg {
|
||||
const char *uuid; /* < The rtmp session id */
|
||||
const char *nickname; /* < This instance's nickname, optional */
|
||||
rtmp_reg_t *next; /* < Next entry */
|
||||
};
|
||||
|
||||
|
||||
typedef enum {
|
||||
MSG_FULLHEADER = 1
|
||||
} rtmp_message_send_flag_t;
|
||||
|
||||
|
||||
/* Invokable functions from flash */
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_connect);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_createStream);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_noop);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_play);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_publish);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_makeCall);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_sendDTMF);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_login);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_logout);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_register);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_unregister);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_answer);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_attach);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_hangup);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_transfer);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_three_way);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_join);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_sendevent);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_receiveaudio);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_receivevideo);
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_log);
|
||||
|
||||
/*** RTMP Sessions ***/
|
||||
rtmp_session_t *rtmp_session_locate(const char *uuid);
|
||||
void rtmp_session_rwunlock(rtmp_session_t *rsession);
|
||||
|
||||
switch_status_t rtmp_session_login(rtmp_session_t *rsession, const char *user, const char *domain);
|
||||
switch_status_t rtmp_session_logout(rtmp_session_t *rsession, const char *user, const char *domain);
|
||||
switch_status_t rtmp_session_check_user(rtmp_session_t *rsession, const char *user, const char *domain);
|
||||
|
||||
switch_status_t rtmp_check_auth(rtmp_session_t *rsession, const char *user, const char *domain, const char *authmd5);
|
||||
void rtmp_event_fill(rtmp_session_t *rsession, switch_event_t *event);
|
||||
switch_status_t amf_object_to_event(amf0_data *obj, switch_event_t **event);
|
||||
switch_status_t amf_event_to_object(amf0_data **obj, switch_event_t *event);
|
||||
|
||||
/*** Endpoint interface ***/
|
||||
switch_call_cause_t rtmp_session_create_call(rtmp_session_t *rsession, switch_core_session_t **newsession, int read_channel, int write_channel, const char *number, const char *auth_user, const char *auth_domain, switch_event_t *event);
|
||||
|
||||
switch_status_t rtmp_on_execute(switch_core_session_t *session);
|
||||
switch_status_t rtmp_send_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf);
|
||||
switch_status_t rtmp_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg);
|
||||
switch_status_t rtmp_receive_event(switch_core_session_t *session, switch_event_t *event);
|
||||
switch_status_t rtmp_on_init(switch_core_session_t *session);
|
||||
switch_status_t rtmp_on_hangup(switch_core_session_t *session);
|
||||
switch_status_t rtmp_on_destroy(switch_core_session_t *session);
|
||||
switch_status_t rtmp_on_routing(switch_core_session_t *session);
|
||||
switch_status_t rtmp_on_exchange_media(switch_core_session_t *session);
|
||||
switch_status_t rtmp_on_soft_execute(switch_core_session_t *session);
|
||||
switch_call_cause_t rtmp_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event,
|
||||
switch_caller_profile_t *outbound_profile,
|
||||
switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags,
|
||||
switch_call_cause_t *cancel_cause);
|
||||
switch_status_t rtmp_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id);
|
||||
switch_status_t rtmp_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id);
|
||||
switch_status_t rtmp_kill_channel(switch_core_session_t *session, int sig);
|
||||
|
||||
switch_status_t rtmp_tech_init(rtmp_private_t *tech_pvt, rtmp_session_t *rtmp_session, switch_core_session_t *session);
|
||||
rtmp_profile_t *rtmp_profile_locate(const char *name);
|
||||
void rtmp_profile_release(rtmp_profile_t *profile);
|
||||
|
||||
/**** I/O ****/
|
||||
switch_status_t rtmp_tcp_init(rtmp_profile_t *profile, const char *bindaddr, rtmp_io_t **new_io, switch_memory_pool_t *pool);
|
||||
switch_status_t rtmp_session_request(rtmp_profile_t *profile, rtmp_session_t **newsession);
|
||||
switch_status_t rtmp_session_destroy(rtmp_session_t **session);
|
||||
|
||||
/**** Protocol ****/
|
||||
void rtmp_set_chunksize(rtmp_session_t *rsession, uint32_t chunksize);
|
||||
switch_status_t rtmp_send_invoke(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...);
|
||||
switch_status_t rtmp_send_invoke_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...);
|
||||
switch_status_t rtmp_send_notify(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...);
|
||||
switch_status_t rtmp_send_notify_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...);
|
||||
switch_status_t rtmp_send_invoke_v(rtmp_session_t *rsession, uint8_t amfnumber, uint8_t type, uint32_t timestamp, uint32_t stream_id, va_list list, switch_bool_t freethem);
|
||||
switch_status_t rtmp_send_message(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint8_t type, uint32_t stream_id, const unsigned char *message, switch_size_t len, uint32_t flags);
|
||||
|
||||
void rtmp_send_event(rtmp_session_t *rsession, switch_event_t *event);
|
||||
void rtmp_notify_call_state(switch_core_session_t *session);
|
||||
void rtmp_send_display_update(switch_core_session_t *session);
|
||||
void rtmp_send_incoming_call(switch_core_session_t *session);
|
||||
void rtmp_send_onhangup(switch_core_session_t *session);
|
||||
void rtmp_add_registration(rtmp_session_t *rsession, const char *auth, const char *nickname);
|
||||
void rtmp_clear_registration(rtmp_session_t *rsession, const char *auth, const char *nickname);
|
||||
/* Attaches an rtmp session to one of its calls, use NULL to hold everything */
|
||||
void rtmp_attach_private(rtmp_session_t *rsession, rtmp_private_t *tech_pvt);
|
||||
rtmp_private_t *rtmp_locate_private(rtmp_session_t *rsession, const char *uuid);
|
||||
void rtmp_ping(rtmp_session_t *rsession);
|
||||
|
||||
void rtmp_session_send_onattach(rtmp_session_t *rsession);
|
||||
|
||||
/* Protocol handler */
|
||||
switch_status_t rtmp_handle_data(rtmp_session_t *rsession);
|
||||
|
||||
#endif /* defined(MOD_RTMP_H) */
|
||||
|
||||
/* For Emacs:
|
||||
* Local Variables:
|
||||
* mode:c
|
||||
* indent-tabs-mode:t
|
||||
* tab-width:4
|
||||
* c-basic-offset:4
|
||||
* End:
|
||||
* For VIM:
|
||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
|
||||
*/
|
|
@ -0,0 +1,913 @@
|
|||
/*
|
||||
* mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2011, Barracuda Networks Inc.
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is Barracuda Networks Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Mathieu Rene <mrene@avgs.ca>
|
||||
*
|
||||
* rtmp.c -- RTMP Protocol Handler
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mod_rtmp.h"
|
||||
|
||||
typedef struct {
|
||||
unsigned char *buf;
|
||||
size_t pos;
|
||||
size_t len;
|
||||
} buffer_helper_t;
|
||||
|
||||
size_t my_buffer_read(void * out_buffer, size_t size, void * user_data)
|
||||
{
|
||||
buffer_helper_t *helper = (buffer_helper_t*)user_data;
|
||||
size_t len = (helper->len - helper->pos) < size ? (helper->len - helper->pos) : size;
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(out_buffer, helper->buf + helper->pos, len);
|
||||
helper->pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t my_buffer_write(const void *buffer, size_t size, void * user_data)
|
||||
{
|
||||
buffer_helper_t *helper = (buffer_helper_t*)user_data;
|
||||
size_t len = (helper->len - helper->pos) < size ? (helper->len - helper->pos) : size;
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(helper->buf + helper->pos, buffer, len);
|
||||
helper->pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
void rtmp_handle_control(rtmp_session_t *rsession, int amfnumber)
|
||||
{
|
||||
rtmp_state_t *state = &rsession->amfstate[amfnumber];
|
||||
char buf[200] = { 0 };
|
||||
char *p = buf;
|
||||
int type = state->buf[0] << 8 | state->buf[1];
|
||||
int i;
|
||||
|
||||
for (i = 2; i < state->origlen; i++) {
|
||||
p += sprintf(p, "%02x ", state->buf[i] & 0xFF);
|
||||
}
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Control (%d): %s\n", type, buf);
|
||||
|
||||
switch(type) {
|
||||
case RTMP_CTRL_STREAM_BEGIN:
|
||||
break;
|
||||
case RTMP_CTRL_PING_REQUEST:
|
||||
{
|
||||
unsigned char buf[] = {
|
||||
INT16(RTMP_CTRL_PING_RESPONSE),
|
||||
state->buf[2], state->buf[3], state->buf[4], state->buf[5]
|
||||
};
|
||||
rtmp_send_message(rsession, amfnumber, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Ping request\n");
|
||||
}
|
||||
break;
|
||||
case RTMP_CTRL_PING_RESPONSE:
|
||||
{
|
||||
uint32_t now = ((switch_micro_time_now()/1000) & 0xFFFFFFFF);
|
||||
uint32_t sent = state->buf[2] << 24 | state->buf[3] << 16 | state->buf[4] << 8 | state->buf[5];
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Ping reply: %d ms\n", (int)(now - sent));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "[amfnumber=%d] Unhandled control packet (type=0x%x)\n",
|
||||
amfnumber, type);
|
||||
}
|
||||
}
|
||||
|
||||
void rtmp_handle_invoke(rtmp_session_t *rsession, int amfnumber)
|
||||
{
|
||||
rtmp_state_t *state = &rsession->amfstate[amfnumber];
|
||||
//amf0_data *dump;
|
||||
int i = 0;
|
||||
buffer_helper_t helper = { state->buf, 0, state->origlen };
|
||||
int64_t transaction_id;
|
||||
const char *command;
|
||||
int argc = 0;
|
||||
amf0_data *argv[100] = { 0 };
|
||||
rtmp_invoke_function_t function;
|
||||
|
||||
#if 0
|
||||
printf(">>>>> BEGIN INVOKE MSG (num=0x%02x, type=0x%02x, stream_id=0x%x)\n", amfnumber, state->type, state->stream_id);
|
||||
while((dump = amf0_data_read(my_buffer_read, &helper))) {
|
||||
amf0_data *dump2;
|
||||
printf("ELM> ");
|
||||
amf0_data_dump(stdout, dump, 0);
|
||||
printf("\n");
|
||||
while ((dump2 = amf0_data_read(my_buffer_read, &helper))) {
|
||||
printf("ELM> ");
|
||||
amf0_data_dump(stdout, dump2, 0);
|
||||
printf("\n");
|
||||
amf0_data_free(dump2);
|
||||
}
|
||||
amf0_data_free(dump);
|
||||
}
|
||||
printf("<<<<< END AMF MSG\n");
|
||||
#endif
|
||||
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
{
|
||||
helper.pos = 0;
|
||||
|
||||
fprintf(rsession->io_debug_in, ">>>>> BEGIN INVOKE MSG (chunk_stream=0x%02x, type=0x%02x, stream_id=0x%x)\n", amfnumber, state->type, state->stream_id);
|
||||
while((dump = amf0_data_read(my_buffer_read, &helper))) {
|
||||
amf0_data *dump2;
|
||||
fprintf(rsession->io_debug_in, "ELM> ");
|
||||
amf0_data_dump(rsession->io_debug_in, dump, 0);
|
||||
fprintf(rsession->io_debug_in, "\n");
|
||||
while ((dump2 = amf0_data_read(my_buffer_read, &helper))) {
|
||||
fprintf(rsession->io_debug_in, "ELM> ");
|
||||
amf0_data_dump(rsession->io_debug_in, dump2, 0);
|
||||
fprintf(rsession->io_debug_in, "\n");
|
||||
amf0_data_free(dump2);
|
||||
}
|
||||
amf0_data_free(dump);
|
||||
}
|
||||
fprintf(rsession->io_debug_in, "<<<<< END AMF MSG\n");
|
||||
fflush(rsession->io_debug_in);
|
||||
}
|
||||
#endif
|
||||
|
||||
helper.pos = 0;
|
||||
while (argc < switch_arraylen(argv) && (argv[argc++] = amf0_data_read(my_buffer_read, &helper)));
|
||||
|
||||
if (!(command = amf0_get_string(argv[i++]))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Bogus INVOKE request\n");
|
||||
return;
|
||||
}
|
||||
|
||||
transaction_id = amf0_get_number(argv[i++]);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[amfnumber=%d] Got INVOKE for %s\n", amfnumber,
|
||||
command);
|
||||
|
||||
if ((function = (rtmp_invoke_function_t)(intptr_t)switch_core_hash_find(rtmp_globals.invoke_hash, command))) {
|
||||
function(rsession, state, amfnumber, transaction_id, argc - 2, argv + 2);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unhandled invoke for \"%s\"\n",
|
||||
command);
|
||||
}
|
||||
|
||||
/* Free all the AMF data we've read */
|
||||
for (i = 0; i < argc; i++) {
|
||||
amf0_data_free(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
switch_status_t rtmp_check_auth(rtmp_session_t *rsession, const char *user, const char *domain, const char *authmd5)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_FALSE;
|
||||
char *auth;
|
||||
char md5[SWITCH_MD5_DIGEST_STRING_SIZE];
|
||||
switch_xml_t xml = NULL, x_param, x_params;
|
||||
switch_bool_t allow_empty_password = SWITCH_FALSE;
|
||||
const char *passwd = NULL;
|
||||
|
||||
/* Locate user */
|
||||
if (switch_xml_locate_user_merged("id", user, domain, NULL, &xml, NULL) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Authentication failed. No such user %s@%s\n", user, domain);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((x_params = switch_xml_child(xml, "params"))) {
|
||||
for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) {
|
||||
const char *var = switch_xml_attr_soft(x_param, "name");
|
||||
const char *val = switch_xml_attr_soft(x_param, "value");
|
||||
|
||||
if (!strcasecmp(var, "password")) {
|
||||
passwd = val;
|
||||
}
|
||||
if (!strcasecmp(var, "allow-empty-password")) {
|
||||
allow_empty_password = switch_true(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zstr(passwd)) {
|
||||
if (allow_empty_password) {
|
||||
status = SWITCH_STATUS_SUCCESS;
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Authentication failed for %s@%s: empty password not allowed\n", user, switch_str_nil(domain));
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
auth = switch_core_sprintf(rsession->pool, "%s:%s@%s:%s", rsession->uuid, user, domain, passwd);
|
||||
switch_md5_string(md5, auth, strlen(auth));
|
||||
|
||||
if (!strncmp(md5, authmd5, SWITCH_MD5_DIGEST_STRING_SIZE)) {
|
||||
status = SWITCH_STATUS_SUCCESS;
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Authentication failed for %s@%s\n", user, domain);
|
||||
}
|
||||
|
||||
done:
|
||||
if (xml) {
|
||||
switch_xml_free(xml);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
switch_status_t amf_object_to_event(amf0_data *obj, switch_event_t **event)
|
||||
{
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
|
||||
if (obj && obj->type == AMF0_TYPE_OBJECT) {
|
||||
amf0_node *node;
|
||||
if (!event) {
|
||||
if ((status = switch_event_create(event, SWITCH_EVENT_CUSTOM)) != SWITCH_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
for (node = amf0_object_first(obj); node; node = amf0_object_next(node)) {
|
||||
const char *name = amf0_get_string(amf0_object_get_name(node));
|
||||
const char *value = amf0_get_string(amf0_object_get_data(node));
|
||||
|
||||
if (!zstr(name) && !zstr(value)) {
|
||||
if (!strcmp(name, "_body")) {
|
||||
switch_event_add_body(*event, "%s", value);
|
||||
} else {
|
||||
switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
switch_status_t amf_event_to_object(amf0_data **obj, switch_event_t *event)
|
||||
{
|
||||
switch_event_header_t *hp;
|
||||
const char *body;
|
||||
|
||||
switch_assert(event);
|
||||
switch_assert(obj);
|
||||
|
||||
if (!*obj) {
|
||||
*obj = amf0_object_new();
|
||||
}
|
||||
|
||||
for (hp = event->headers; hp; hp = hp->next) {
|
||||
amf0_object_add(*obj, hp->name, amf0_str(hp->value));
|
||||
}
|
||||
|
||||
body = switch_event_get_body(event);
|
||||
if (!zstr(body)) {
|
||||
amf0_object_add(*obj, "_body", amf0_str(body));
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void rtmp_set_chunksize(rtmp_session_t *rsession, uint32_t chunksize)
|
||||
{
|
||||
if (rsession->out_chunksize != chunksize) {
|
||||
unsigned char buf[] = {
|
||||
INT32(chunksize)
|
||||
};
|
||||
|
||||
rtmp_send_message(rsession, 2 /*amfnumber*/, 0, RTMP_TYPE_CHUNKSIZE, 0, buf, sizeof(buf), MSG_FULLHEADER);
|
||||
rsession->out_chunksize = chunksize;
|
||||
}
|
||||
}
|
||||
|
||||
void rtmp_get_user_variables(switch_event_t **event, switch_core_session_t *session)
|
||||
{
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
switch_event_header_t *he;
|
||||
|
||||
if (!*event && switch_event_create(event, SWITCH_EVENT_CLONE) != SWITCH_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((he = switch_channel_variable_first(channel))) {
|
||||
for (; he; he = he->next) {
|
||||
if (!strncmp(he->name, RTMP_USER_VARIABLE_PREFIX, strlen(RTMP_USER_VARIABLE_PREFIX))) {
|
||||
switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, he->name, he->value);
|
||||
}
|
||||
}
|
||||
switch_channel_variable_last(channel);
|
||||
}
|
||||
}
|
||||
|
||||
void rtmp_session_send_onattach(rtmp_session_t *rsession)
|
||||
{
|
||||
const char *uuid = "";
|
||||
|
||||
if (rsession->tech_pvt) {
|
||||
uuid = switch_core_session_get_uuid(rsession->tech_pvt->session);
|
||||
}
|
||||
|
||||
rtmp_send_invoke_free(rsession, 3, 0, 0,
|
||||
amf0_str("onAttach"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str(uuid), NULL);
|
||||
|
||||
}
|
||||
|
||||
void rtmp_send_display_update(switch_core_session_t *session)
|
||||
{
|
||||
rtmp_private_t *tech_pvt = switch_core_session_get_private(session);
|
||||
|
||||
rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0,
|
||||
amf0_str("displayUpdate"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str(switch_core_session_get_uuid(session)),
|
||||
amf0_str(switch_str_nil(tech_pvt->display_callee_id_name)),
|
||||
amf0_str(switch_str_nil(tech_pvt->display_callee_id_number)), NULL);
|
||||
}
|
||||
|
||||
void rtmp_send_incoming_call(switch_core_session_t *session)
|
||||
{
|
||||
rtmp_private_t *tech_pvt = switch_core_session_get_private(session);
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
switch_caller_profile_t *caller_profile = switch_channel_get_caller_profile(channel);
|
||||
switch_event_t *event = NULL;
|
||||
amf0_data *obj = NULL;
|
||||
|
||||
rtmp_get_user_variables(&event, session);
|
||||
|
||||
if (event) {
|
||||
amf_event_to_object(&obj, event);
|
||||
switch_event_destroy(&event);
|
||||
}
|
||||
|
||||
rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0,
|
||||
amf0_str("incomingCall"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str(switch_core_session_get_uuid(session)),
|
||||
amf0_str(switch_str_nil(caller_profile->caller_id_name)),
|
||||
amf0_str(switch_str_nil(caller_profile->caller_id_number)),
|
||||
!zstr(tech_pvt->auth) ? amf0_str(tech_pvt->auth) : amf0_null_new(),
|
||||
obj ? obj : amf0_null_new(), NULL);
|
||||
}
|
||||
|
||||
void rtmp_send_onhangup(switch_core_session_t *session)
|
||||
{
|
||||
rtmp_private_t *tech_pvt = switch_core_session_get_private(session);
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
|
||||
rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0,
|
||||
amf0_str("onHangup"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str(switch_core_session_get_uuid(session)),
|
||||
amf0_str(switch_channel_cause2str(switch_channel_get_cause(channel))), NULL);
|
||||
}
|
||||
|
||||
void rtmp_send_event(rtmp_session_t *rsession, switch_event_t *event)
|
||||
{
|
||||
amf0_data *obj = NULL;
|
||||
|
||||
switch_assert(event != NULL);
|
||||
switch_assert(rsession != NULL);
|
||||
|
||||
if (amf_event_to_object(&obj, event) == SWITCH_STATUS_SUCCESS) {
|
||||
rtmp_send_invoke_free(rsession, 3, 0, 0, amf0_str("event"), amf0_number_new(0), amf0_null_new(), obj, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void rtmp_ping(rtmp_session_t *rsession)
|
||||
{
|
||||
uint32_t now = (uint32_t)((switch_micro_time_now() / 1000) & 0xFFFFFFFF);
|
||||
unsigned char buf[] = {
|
||||
INT16(RTMP_CTRL_PING_REQUEST),
|
||||
INT32(now)
|
||||
};
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
|
||||
}
|
||||
|
||||
void rtmp_notify_call_state(switch_core_session_t *session)
|
||||
{
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
const char *state = switch_channel_callstate2str(switch_channel_get_callstate(channel));
|
||||
rtmp_private_t *tech_pvt = switch_core_session_get_private(session);
|
||||
|
||||
rtmp_send_invoke_free(tech_pvt->rtmp_session, 3, 0, 0,
|
||||
amf0_str("callState"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str(switch_core_session_get_uuid(session)),
|
||||
amf0_str(state), NULL);
|
||||
}
|
||||
|
||||
switch_status_t rtmp_send_invoke(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...)
|
||||
{
|
||||
switch_status_t s;
|
||||
va_list list;
|
||||
va_start(list, stream_id);
|
||||
s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_INVOKE, timestamp, stream_id, list, SWITCH_FALSE);
|
||||
va_end(list);
|
||||
return s;
|
||||
}
|
||||
|
||||
switch_status_t rtmp_send_invoke_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...)
|
||||
{
|
||||
switch_status_t s;
|
||||
va_list list;
|
||||
va_start(list, stream_id);
|
||||
s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_INVOKE, timestamp, stream_id, list, SWITCH_TRUE);
|
||||
va_end(list);
|
||||
return s;
|
||||
}
|
||||
|
||||
switch_status_t rtmp_send_notify(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...)
|
||||
{
|
||||
switch_status_t s;
|
||||
va_list list;
|
||||
va_start(list, stream_id);
|
||||
s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_NOTIFY, timestamp, stream_id, list, SWITCH_FALSE);
|
||||
va_end(list);
|
||||
return s;
|
||||
}
|
||||
|
||||
switch_status_t rtmp_send_notify_free(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint32_t stream_id, ...)
|
||||
{
|
||||
switch_status_t s;
|
||||
va_list list;
|
||||
va_start(list, stream_id);
|
||||
s = rtmp_send_invoke_v(rsession, amfnumber, RTMP_TYPE_NOTIFY, timestamp, stream_id, list, SWITCH_TRUE);
|
||||
va_end(list);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
switch_status_t rtmp_send_invoke_v(rtmp_session_t *rsession, uint8_t amfnumber, uint8_t type, uint32_t timestamp, uint32_t stream_id, va_list list, switch_bool_t freethem)
|
||||
{
|
||||
amf0_data *data;
|
||||
unsigned char buf[AMF_MAX_SIZE];
|
||||
buffer_helper_t helper = { buf, 0, AMF_MAX_SIZE };
|
||||
|
||||
while ((data = va_arg(list, amf0_data*))) {
|
||||
//amf0_data_dump(stdout, data, 0);
|
||||
//printf("\n");
|
||||
amf0_data_write(data, my_buffer_write, &helper);
|
||||
if (freethem) {
|
||||
amf0_data_free(data);
|
||||
}
|
||||
}
|
||||
return rtmp_send_message(rsession, amfnumber, timestamp, type, stream_id, buf, helper.pos, 0);
|
||||
}
|
||||
|
||||
/* Break message down into 128 bytes chunks, add the appropriate headers and send it out */
|
||||
switch_status_t rtmp_send_message(rtmp_session_t *rsession, uint8_t amfnumber, uint32_t timestamp, uint8_t type, uint32_t stream_id, const unsigned char *message, switch_size_t len, uint32_t flags)
|
||||
{
|
||||
switch_size_t pos = 0;
|
||||
uint8_t header[12] = { amfnumber & 0x3F, INT24(0), INT24(len), type, INT32_LE(stream_id) };
|
||||
switch_size_t chunksize;
|
||||
uint8_t microhdr = (3 << 6) | amfnumber;
|
||||
switch_size_t hdrsize = 1;
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
rtmp_state_t *state = &rsession->amfstate_out[amfnumber];
|
||||
|
||||
if ((rsession->send_ack + rsession->send_ack_window) < rsession->send &&
|
||||
(type == RTMP_TYPE_VIDEO || type == RTMP_TYPE_AUDIO)) {
|
||||
/* We're sending too fast, drop the frame */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "DROP %s FRAME [amfnumber=%d type=0x%x stream_id=0x%x] len=%"SWITCH_SIZE_T_FMT" \n",
|
||||
type == RTMP_TYPE_AUDIO ? "AUDIO" : "VIDEO", amfnumber, type, stream_id, len);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (type != RTMP_TYPE_AUDIO && type != RTMP_TYPE_VIDEO && type != RTMP_TYPE_ACK) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[amfnumber=%d type=0x%x stream_id=0x%x] len=%"SWITCH_SIZE_T_FMT" \n", amfnumber, type, stream_id, len);
|
||||
}
|
||||
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
{
|
||||
fprintf(rsession->io_debug_out, "[amfnumber=%d type=0x%x stream_id=0x%x] len=%"SWITCH_SIZE_T_FMT" \n", amfnumber, type, stream_id, len);
|
||||
if (type == RTMP_TYPE_INVOKE || type == RTMP_TYPE_NOTIFY) {
|
||||
buffer_helper_t helper = { (unsigned char*)message, 0, len };
|
||||
amf0_data *dump;
|
||||
while((dump = amf0_data_read(my_buffer_read, &helper))) {
|
||||
amf0_data *dump2;
|
||||
fprintf(rsession->io_debug_out, "ELM> ");
|
||||
amf0_data_dump(rsession->io_debug_out, dump, 0);
|
||||
fprintf(rsession->io_debug_out, "\n");
|
||||
while ((dump2 = amf0_data_read(my_buffer_read, &helper))) {
|
||||
fprintf(rsession->io_debug_out, "ELM> ");
|
||||
amf0_data_dump(rsession->io_debug_out, dump2, 0);
|
||||
fprintf(rsession->io_debug_out, "\n");
|
||||
amf0_data_free(dump2);
|
||||
}
|
||||
amf0_data_free(dump);
|
||||
}
|
||||
fprintf(rsession->io_debug_out, "<<<<< END AMF MSG\n");
|
||||
}
|
||||
fflush(rsession->io_debug_out);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Find out what is the smallest header we can use */
|
||||
if (!(flags & MSG_FULLHEADER) && stream_id > 0 && state->stream_id == stream_id && timestamp >= state->ts) {
|
||||
if (state->type == type && state->origlen == len) {
|
||||
if (state->ts == timestamp) {
|
||||
/* Type 3: no header! */
|
||||
hdrsize = 1;
|
||||
header[0] |= 3 << 6;
|
||||
} else {
|
||||
uint32_t delta = timestamp - state->ts;
|
||||
/* Type 2: timestamp delta */
|
||||
hdrsize = 4;
|
||||
header[0] |= 2 << 6;
|
||||
header[1] = (delta >> 16) & 0xFF;
|
||||
header[2] = (delta >> 8) & 0xFF;
|
||||
header[3] = delta & 0xFF;
|
||||
}
|
||||
} else {
|
||||
/* Type 1: ts delta + msg len + type */
|
||||
uint32_t delta = timestamp - state->ts;
|
||||
hdrsize = 8;
|
||||
header[0] |= 1 << 6;
|
||||
header[1] = (delta >> 16) & 0xFF;
|
||||
header[2] = (delta >> 8) & 0xFF;
|
||||
header[3] = delta & 0xFF;
|
||||
}
|
||||
} else {
|
||||
hdrsize = 12; /* Type 0, full header */
|
||||
header[1] = (timestamp >> 16) & 0xFF;
|
||||
header[2] = (timestamp >> 8) & 0xFF;
|
||||
header[3] = timestamp & 0xFF;
|
||||
}
|
||||
|
||||
state->ts = timestamp;
|
||||
state->type = type;
|
||||
state->origlen = len;
|
||||
state->stream_id = stream_id;
|
||||
|
||||
switch_mutex_lock(rsession->socket_mutex);
|
||||
chunksize = (len - pos) < rsession->out_chunksize ? (len - pos) : rsession->out_chunksize;
|
||||
if (rsession->profile->io->write(rsession, (unsigned char*)header, &hdrsize) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_goto_status(SWITCH_STATUS_FALSE, end);
|
||||
}
|
||||
rsession->send += hdrsize;
|
||||
|
||||
/* Write one chunk of data */
|
||||
if (rsession->profile->io->write(rsession, (unsigned char*)message, &chunksize) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_goto_status(SWITCH_STATUS_FALSE, end);
|
||||
}
|
||||
rsession->send += chunksize;
|
||||
pos += chunksize;
|
||||
|
||||
/* Send more chunks if we need to */
|
||||
while (((signed)len - (signed)pos) > 0) {
|
||||
switch_mutex_unlock(rsession->socket_mutex);
|
||||
/* Let other threads send data on the socket */
|
||||
switch_mutex_lock(rsession->socket_mutex);
|
||||
hdrsize = 1;
|
||||
if (rsession->profile->io->write(rsession, (unsigned char*)µhdr, &hdrsize) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_goto_status(SWITCH_STATUS_FALSE, end);
|
||||
}
|
||||
rsession->send += hdrsize;
|
||||
|
||||
chunksize = (len - pos) < rsession->out_chunksize ? (len - pos) : rsession->out_chunksize;
|
||||
|
||||
if (rsession->profile->io->write(rsession, message + pos, &chunksize) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_goto_status(SWITCH_STATUS_FALSE, end);
|
||||
}
|
||||
rsession->send += chunksize;
|
||||
pos += chunksize;
|
||||
}
|
||||
end:
|
||||
switch_mutex_unlock(rsession->socket_mutex);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* Returns SWITCH_STATUS_SUCCESS of the connection is still active or SWITCH_STATUS_FALSE to tear it down */
|
||||
switch_status_t rtmp_handle_data(rtmp_session_t *rsession)
|
||||
{
|
||||
uint8_t buf[RTMP_TCP_READ_BUF];
|
||||
switch_size_t s = RTMP_TCP_READ_BUF;
|
||||
|
||||
if (rsession->state == RS_HANDSHAKE) {
|
||||
s = 1537 - rsession->hspos;
|
||||
|
||||
if (rsession->profile->io->read(rsession, rsession->hsbuf + rsession->hspos, &s) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
rsession->hspos += s;
|
||||
|
||||
/* Receive C0 and C1 */
|
||||
if (rsession->hspos < 1537) {
|
||||
/* Not quite there yet */
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* Send reply (S0 + S1) */
|
||||
memset(buf, 0, sizeof(buf));
|
||||
*buf = '\x03';
|
||||
s = 1537;
|
||||
rsession->profile->io->write(rsession, (unsigned char*)buf, &s);
|
||||
|
||||
/* Send S2 */
|
||||
s = 1536;
|
||||
rsession->profile->io->write(rsession, rsession->hsbuf, &s);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sent handshake response\n");
|
||||
|
||||
rsession->state++;
|
||||
rsession->hspos = 0;
|
||||
} else if (rsession->state == RS_HANDSHAKE2) {
|
||||
s = 1536 - rsession->hspos;
|
||||
|
||||
/* Receive C2 */
|
||||
if (rsession->profile->io->read(rsession, rsession->hsbuf + rsession->hspos, &s) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
rsession->hspos += s;
|
||||
|
||||
if (rsession->hspos < 1536) {
|
||||
/* Not quite there yet */
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
rsession->state++;
|
||||
|
||||
//s = 1536;
|
||||
//rsession->profile->io->write(rsession, (char*)buf, &s);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Done with handshake\n");
|
||||
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
} else if (rsession->state == RS_ESTABLISHED) {
|
||||
/* Process RTMP packet */
|
||||
switch(rsession->parse_state) {
|
||||
case 0:
|
||||
// Read the header's first byte
|
||||
s = 1;
|
||||
if (rsession->profile->io->read(rsession, (unsigned char*)buf, &s) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
rsession->recv += s;
|
||||
|
||||
switch(buf[0] >> 6) {
|
||||
case 0:
|
||||
rsession->hdrsize = 12;
|
||||
break;
|
||||
case 1:
|
||||
rsession->hdrsize = 8;
|
||||
break;
|
||||
case 2:
|
||||
rsession->hdrsize = 4;
|
||||
break;
|
||||
case 3:
|
||||
rsession->hdrsize = 1;
|
||||
break;
|
||||
default:
|
||||
rsession->hdrsize = 0;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "WTF hdrsize 0x%02x %d\n", *buf, *buf >> 6);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
rsession->amfnumber = buf[0] & 0x3F; /* Get rid of the 2 first bits */
|
||||
if (rsession->amfnumber > 64) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Protocol error\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Header size: %d AMF Number: %d\n", rsession->hdrsize, rsession->amfnumber);
|
||||
rsession->parse_state++;
|
||||
if (rsession->hdrsize == 1) {
|
||||
/* Skip header fetch on one-byte headers since we have it already */
|
||||
rsession->parse_state++;
|
||||
}
|
||||
rsession->parse_remain = 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
/* Read full header and decode */
|
||||
rtmp_state_t *state = &rsession->amfstate[rsession->amfnumber];
|
||||
uint8_t *hdr = (uint8_t*)state->header.sz;
|
||||
unsigned char *readbuf = (unsigned char*)hdr;
|
||||
|
||||
if (!rsession->parse_remain) {
|
||||
rsession->parse_remain = s = rsession->hdrsize - 1;
|
||||
} else {
|
||||
s = rsession->parse_remain;
|
||||
readbuf += (rsession->hdrsize - 1) - s;
|
||||
}
|
||||
|
||||
switch_assert(s < 12 && s > 0); /** XXX **/
|
||||
|
||||
if (rsession->profile->io->read(rsession, readbuf, &s) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
rsession->parse_remain -= s;
|
||||
if (rsession->parse_remain > 0) {
|
||||
/* More data please */
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
rsession->recv += s;
|
||||
|
||||
if (rsession->hdrsize == 12) {
|
||||
state->ts = (hdr[0] << 16) | (hdr[1] << 8) | (hdr[2]);
|
||||
state->ts_delta = 0;
|
||||
} else if (rsession->hdrsize >= 4) {
|
||||
/* Save the timestamp delta since we have to re-use it with type 3 headers */
|
||||
state->ts_delta = (hdr[0] << 16) | (hdr[1] << 8) | (hdr[2]);
|
||||
state->ts += state->ts_delta;
|
||||
} else if (rsession->hdrsize == 1) {
|
||||
/* Type 3: Re-use timestamp delta if we have one */
|
||||
state->ts += state->ts_delta;
|
||||
}
|
||||
|
||||
if (rsession->hdrsize >= 8) {
|
||||
/* Reset length counter since its included in the header */
|
||||
state->remainlen = state->origlen = (hdr[3] << 16) | (hdr[4] << 8) | (hdr[5]);
|
||||
state->buf_pos = 0;
|
||||
state->type = hdr[6];
|
||||
}
|
||||
if (rsession->hdrsize == 12) {
|
||||
state->stream_id = (hdr[10] << 24) | (hdr[9] << 16) | (hdr[8] << 8) | hdr[7];
|
||||
}
|
||||
|
||||
if (rsession->hdrsize >= 8 && state->origlen == 0) {
|
||||
/* Happens we sometimes get a 0 length packet */
|
||||
rsession->parse_state = 0;
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* FIXME: Handle extended timestamps */
|
||||
if (state->ts == 0x00ffffff) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
rsession->parse_state++;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
rtmp_state_t *state = &rsession->amfstate[rsession->amfnumber];
|
||||
|
||||
if (rsession->parse_remain > 0) {
|
||||
s = rsession->parse_remain;
|
||||
} else {
|
||||
s = state->remainlen < rsession->in_chunksize ? state->remainlen : rsession->in_chunksize;
|
||||
rsession->parse_remain = s;
|
||||
}
|
||||
|
||||
if (!s) {
|
||||
/* Restart from beginning */
|
||||
s = state->remainlen = state->origlen;
|
||||
rsession->parse_remain = s;
|
||||
if (!s) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Protocol error, forcing big read\n");
|
||||
s = sizeof(state->buf);
|
||||
rsession->profile->io->read(rsession, state->buf, &s);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sanity check */
|
||||
if ((state->buf_pos + s) > AMF_MAX_SIZE) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "WTF %"SWITCH_SIZE_T_FMT" %"SWITCH_SIZE_T_FMT"\n",
|
||||
state->buf_pos, s);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Protocol error: exceeding max AMF packet size\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
switch_assert(s <= rsession->in_chunksize);
|
||||
|
||||
if (rsession->profile->io->read(rsession, state->buf + state->buf_pos, &s) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Read error\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
rsession->recv += s;
|
||||
|
||||
state->remainlen -= s;
|
||||
rsession->parse_remain -= s;
|
||||
state->buf_pos += s;
|
||||
|
||||
if (rsession->parse_remain > 0) {
|
||||
/* Need more data */
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (state->remainlen == 0) {
|
||||
|
||||
if (state->type != RTMP_TYPE_AUDIO && state->type != RTMP_TYPE_VIDEO && state->type != RTMP_TYPE_ACK) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "[chunk_stream=%d type=0x%x ts=%d stream_id=0x%x] len=%d\n", rsession->amfnumber, state->type, (int)state->ts, state->stream_id, state->origlen);
|
||||
}
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
fprintf(rsession->io_debug_in, "[chunk_stream=%d type=0x%x ts=%d stream_id=0x%x] len=%d\n", rsession->amfnumber, state->type, (int)state->ts, state->stream_id, state->origlen);
|
||||
#endif
|
||||
switch(state->type) {
|
||||
case RTMP_TYPE_CHUNKSIZE:
|
||||
rsession->in_chunksize = state->buf[0] << 24 | state->buf[1] << 16 | state->buf[2] << 8 | state->buf[3];
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SET CHUNKSIZE=%d\n", (int)rsession->in_chunksize);
|
||||
break;
|
||||
case RTMP_TYPE_USERCTRL:
|
||||
rtmp_handle_control(rsession, rsession->amfnumber);
|
||||
break;
|
||||
case RTMP_TYPE_INVOKE:
|
||||
rtmp_handle_invoke(rsession, rsession->amfnumber);
|
||||
break;
|
||||
case RTMP_TYPE_AUDIO: /* Audio data */
|
||||
if (rsession->tech_pvt) {
|
||||
uint16_t len = state->origlen;
|
||||
switch_mutex_lock(rsession->tech_pvt->readbuf_mutex);
|
||||
switch_buffer_write(rsession->tech_pvt->readbuf, &len, 2);
|
||||
switch_buffer_write(rsession->tech_pvt->readbuf, state->buf, len);
|
||||
switch_mutex_unlock(rsession->tech_pvt->readbuf_mutex);
|
||||
}
|
||||
break;
|
||||
case RTMP_TYPE_VIDEO: /* Video data */
|
||||
case RTMP_TYPE_METADATA: /* Metadata */
|
||||
break;
|
||||
case RTMP_TYPE_WINDOW_ACK_SIZE:
|
||||
rsession->send_ack_window = (state->buf[0] << 24) | (state->buf[1] << 16) | (state->buf[2] << 8) | (state->buf[3]);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Set window size: %lu bytes\n", (long unsigned int)rsession->send_ack_window);
|
||||
break;
|
||||
case RTMP_TYPE_ACK:
|
||||
{
|
||||
switch_time_t now = switch_micro_time_now();
|
||||
uint32_t ack = (state->buf[0] << 24) | (state->buf[1] << 16) | (state->buf[2] << 8) | (state->buf[3]);
|
||||
uint32_t delta = rsession->send_ack_ts == 0 ? 0 : now - rsession->send_ack_ts;
|
||||
|
||||
delta /= 1000000; /* microseconds -> seconds */
|
||||
|
||||
if (delta) {
|
||||
rsession->send_bw = (ack - rsession->send_ack) / delta;
|
||||
}
|
||||
|
||||
rsession->send_ack = ack;
|
||||
rsession->send_ack_ts = switch_micro_time_now();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot handle message type 0x%x\n", state->type);
|
||||
break;
|
||||
}
|
||||
state->buf_pos = 0;
|
||||
}
|
||||
|
||||
rsession->parse_state = 0;
|
||||
|
||||
/* Send an ACK if we need to */
|
||||
if (rsession->recv - rsession->recv_ack_sent >= rsession->recv_ack_window) {
|
||||
unsigned char ackbuf[] = { INT32(rsession->recv) };
|
||||
|
||||
rtmp_send_message(rsession, 2/*chunkstream*/, 0/*ts*/, RTMP_TYPE_ACK, 0/*msg stream id */, ackbuf, sizeof(ackbuf), 0 /*flags*/);
|
||||
rsession->recv_ack_sent = rsession->recv;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* For Emacs:
|
||||
* Local Variables:
|
||||
* mode:c
|
||||
* indent-tabs-mode:t
|
||||
* tab-width:4
|
||||
* c-basic-offset:4
|
||||
* End:
|
||||
* For VIM:
|
||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
|
||||
*/
|
|
@ -0,0 +1,841 @@
|
|||
/*
|
||||
* mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2011, Barracuda Networks Inc.
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is Barracuda Networks Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Mathieu Rene <mrene@avgs.ca>
|
||||
*
|
||||
* rtmp.c -- RTMP Signalling functions
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mod_rtmp.h"
|
||||
|
||||
/* AMF */
|
||||
#include "amf0.h"
|
||||
#include "io.h"
|
||||
#include "types.h"
|
||||
|
||||
/* RTMP_INVOKE_FUNCTION is a macro that expands to:
|
||||
switch_status_t function(rtmp_session_t *rsession, rtmp_state_t *state, int amfnumber, int transaction_id, int argc, amf0_data *argv[])
|
||||
*/
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_connect)
|
||||
{
|
||||
amf0_data *object1 = amf0_object_new(), *object2 = amf0_object_new(), *params = argv[0], *d;
|
||||
const char *s;
|
||||
|
||||
if ((d = amf0_object_get(params, "app")) && (s = amf0_get_string(d))) {
|
||||
rsession->app = switch_core_strdup(rsession->pool, s);
|
||||
}
|
||||
|
||||
if ((d = amf0_object_get(params, "flashVer")) && (s = amf0_get_string(d))) {
|
||||
rsession->flashVer = switch_core_strdup(rsession->pool, s);
|
||||
}
|
||||
if ((d = amf0_object_get(params, "swfUrl")) && (s = amf0_get_string(d))) {
|
||||
rsession->swfUrl = switch_core_strdup(rsession->pool, s);
|
||||
}
|
||||
if ((d = amf0_object_get(params, "tcUrl")) && (s = amf0_get_string(d))) {
|
||||
rsession->tcUrl = switch_core_strdup(rsession->pool, s);
|
||||
}
|
||||
if ((d = amf0_object_get(params, "pageUrl")) && (s = amf0_get_string(d))) {
|
||||
rsession->pageUrl = switch_core_strdup(rsession->pool, s);
|
||||
}
|
||||
|
||||
if ((d = amf0_object_get(params, "capabilities"))) {
|
||||
rsession->capabilities = amf0_get_number(d);
|
||||
}
|
||||
if ((d = amf0_object_get(params, "audioCodecs"))) {
|
||||
rsession->audioCodecs = amf0_get_number(d);
|
||||
}
|
||||
if ((d = amf0_object_get(params, "videoCodecs"))) {
|
||||
rsession->videoCodecs = amf0_get_number(d);
|
||||
}
|
||||
if ((d = amf0_object_get(params, "videoFunction"))) {
|
||||
rsession->videoFunction = amf0_get_number(d);
|
||||
}
|
||||
|
||||
amf0_object_add(object1, "fmsVer", amf0_number_new(1));
|
||||
amf0_object_add(object1, "capabilities", amf0_number_new(31));
|
||||
|
||||
amf0_object_add(object2, "level", amf0_str("status"));
|
||||
amf0_object_add(object2, "code", amf0_str("NetConnection.Connect.Success"));
|
||||
amf0_object_add(object2, "description", amf0_str("Connection succeeded"));
|
||||
amf0_object_add(object2, "clientId", amf0_number_new(217834719));
|
||||
amf0_object_add(object2, "objectEncoding", amf0_number_new(0));
|
||||
|
||||
rtmp_set_chunksize(rsession, rsession->profile->chunksize);
|
||||
|
||||
{
|
||||
unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW) };
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_WINDOW_ACK_SIZE, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER);
|
||||
}
|
||||
|
||||
{
|
||||
unsigned char ackbuf[] = { INT32(RTMP_DEFAULT_ACK_WINDOW), 0x1 /* Soft limit */};
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_SET_PEER_BW, 0, ackbuf, sizeof(ackbuf), MSG_FULLHEADER);
|
||||
}
|
||||
|
||||
{
|
||||
unsigned char buf[] = {
|
||||
INT16(RTMP_CTRL_STREAM_BEGIN),
|
||||
INT32(0)
|
||||
};
|
||||
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
|
||||
}
|
||||
|
||||
/* respond with a success message */
|
||||
rtmp_send_invoke_free(rsession, amfnumber, 0, 0,
|
||||
amf0_str("_result"),
|
||||
amf0_number_new(1),
|
||||
object1,
|
||||
object2,
|
||||
NULL);
|
||||
|
||||
rtmp_send_invoke_free(rsession, 3, 0, 0,
|
||||
amf0_str("connected"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str(rsession->uuid), NULL);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Sent connect reply\n");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_createStream)
|
||||
{
|
||||
rtmp_send_invoke_free(rsession, amfnumber, 0, 0,
|
||||
amf0_str("_result"),
|
||||
amf0_number_new(transaction_id),
|
||||
amf0_null_new(),
|
||||
amf0_number_new(rsession->next_streamid),
|
||||
NULL);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Replied to createStream (%u)\n", rsession->next_streamid);
|
||||
|
||||
rsession->next_streamid++;
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_noop)
|
||||
{
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_receiveaudio)
|
||||
{
|
||||
switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE;
|
||||
|
||||
if (enabled) {
|
||||
switch_set_flag(rsession, SFLAG_AUDIO);
|
||||
} else {
|
||||
switch_clear_flag(rsession, SFLAG_AUDIO);
|
||||
}
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%sending audio\n", enabled ? "S" : "Not s");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_receivevideo)
|
||||
{
|
||||
switch_bool_t enabled = argv[1] ? amf0_boolean_get_value(argv[1]) : SWITCH_FALSE;
|
||||
|
||||
if (enabled) {
|
||||
switch_set_flag(rsession, SFLAG_VIDEO);
|
||||
if (rsession->tech_pvt) {
|
||||
switch_set_flag(rsession->tech_pvt, TFLAG_VID_WAIT_KEYFRAME);
|
||||
}
|
||||
} else {
|
||||
switch_clear_flag(rsession, SFLAG_VIDEO);
|
||||
}
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%sending video\n", enabled ? "S" : "Not s");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_play)
|
||||
{
|
||||
amf0_data *obj = amf0_object_new();
|
||||
amf0_data *object = amf0_object_new();
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Got play for %s on stream %d\n", switch_str_nil(amf0_get_string(argv[1])),
|
||||
state->stream_id);
|
||||
|
||||
/* Set outgoing chunk size to 1024 bytes */
|
||||
rtmp_set_chunksize(rsession, 1024);
|
||||
|
||||
rsession->media_streamid = state->stream_id;
|
||||
|
||||
/* Send StreamBegin on the current stream */
|
||||
{
|
||||
unsigned char buf[] = {
|
||||
INT16(RTMP_CTRL_STREAM_BEGIN),
|
||||
INT32(rsession->media_streamid)
|
||||
};
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
unsigned char buf[] = {
|
||||
INT16(RTMP_CTRL_SET_BUFFER_LENGTH),
|
||||
INT32(rsession->media_streamid),
|
||||
INT32(rsession->profile->buffer_len)
|
||||
};
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
|
||||
}
|
||||
|
||||
/* Send onStatus */
|
||||
amf0_object_add(object, "level", amf0_str("status"));
|
||||
amf0_object_add(object, "code", amf0_str("NetStream.Play.Reset"));
|
||||
amf0_object_add(object, "description", amf0_str("description"));
|
||||
amf0_object_add(object, "details", amf0_str("details"));
|
||||
amf0_object_add(object, "clientid", amf0_number_new(217834719));
|
||||
|
||||
rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
|
||||
amf0_str("onStatus"),
|
||||
amf0_number_new(1),
|
||||
amf0_null_new(),
|
||||
object, NULL);
|
||||
|
||||
object = amf0_object_new();
|
||||
|
||||
amf0_object_add(object, "level", amf0_str("status"));
|
||||
amf0_object_add(object, "code", amf0_str("NetStream.Play.Start"));
|
||||
amf0_object_add(object, "description", amf0_str("description"));
|
||||
amf0_object_add(object, "details", amf0_str("details"));
|
||||
amf0_object_add(object, "clientid", amf0_number_new(217834719));
|
||||
|
||||
rtmp_send_invoke_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
|
||||
amf0_str("onStatus"),
|
||||
amf0_number_new(1),
|
||||
amf0_null_new(),
|
||||
object, NULL);
|
||||
|
||||
amf0_object_add(obj, "code", amf0_str("NetStream.Data.Start"));
|
||||
|
||||
rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
|
||||
amf0_str("onStatus"),
|
||||
obj, NULL);
|
||||
|
||||
rtmp_send_notify_free(rsession, RTMP_DEFAULT_STREAM_NOTIFY, 0, rsession->media_streamid,
|
||||
amf0_str("|RtmpSampleAccess"),
|
||||
amf0_boolean_new(1),
|
||||
amf0_boolean_new(1), NULL);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_publish)
|
||||
{
|
||||
|
||||
unsigned char buf[] = {
|
||||
INT16(RTMP_CTRL_STREAM_BEGIN),
|
||||
INT32(state->stream_id)
|
||||
};
|
||||
|
||||
rtmp_send_message(rsession, 2, 0, RTMP_TYPE_USERCTRL, 0, buf, sizeof(buf), 0);
|
||||
|
||||
rtmp_send_invoke_free(rsession, amfnumber, 0, 0,
|
||||
amf0_str("_result"),
|
||||
amf0_number_new(transaction_id),
|
||||
amf0_null_new(),
|
||||
amf0_null_new(),
|
||||
NULL);
|
||||
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got publish on stream %u.\n", state->stream_id);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_makeCall)
|
||||
{
|
||||
switch_core_session_t *newsession = NULL;
|
||||
char *number = NULL;
|
||||
|
||||
if ((number = amf0_get_string(argv[1]))) {
|
||||
switch_event_t *event = NULL;
|
||||
char *auth, *user = NULL, *domain = NULL;
|
||||
|
||||
if ((auth = amf0_get_string(argv[2])) && !zstr(auth)) {
|
||||
switch_split_user_domain(auth, &user, &domain);
|
||||
if (rtmp_session_check_user(rsession, user, domain) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in account [%s@%s]\n",
|
||||
number, switch_str_nil(user), switch_str_nil(domain));
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
} else if (rsession->profile->auth_calls && !rsession->account) {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_WARNING, "Unauthorized call to %s, client is not logged in\n", number);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (amf0_is_object(argv[3])) {
|
||||
amf_object_to_event(argv[3], &event);
|
||||
}
|
||||
|
||||
if (rtmp_session_create_call(rsession, &newsession, 0, RTMP_DEFAULT_STREAM_AUDIO, number, user, domain, event) != SWITCH_CAUSE_NONE) {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create call.\n");
|
||||
}
|
||||
|
||||
if (event) {
|
||||
switch_event_destroy(&event);
|
||||
}
|
||||
}
|
||||
|
||||
if (newsession) {
|
||||
rtmp_private_t *new_pvt = switch_core_session_get_private(newsession);
|
||||
rtmp_send_invoke_free(rsession, 3, 0, 0,
|
||||
amf0_str("onMakeCall"),
|
||||
amf0_number_new(transaction_id),
|
||||
amf0_null_new(),
|
||||
amf0_str(switch_core_session_get_uuid(newsession)),
|
||||
amf0_str(switch_str_nil(number)),
|
||||
amf0_str(switch_str_nil(new_pvt->auth)),
|
||||
NULL);
|
||||
|
||||
rtmp_attach_private(rsession, switch_core_session_get_private(newsession));
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_sendDTMF)
|
||||
{
|
||||
/* Send DTMFs on the active channel */
|
||||
switch_dtmf_t dtmf = { 0 };
|
||||
switch_channel_t *channel;
|
||||
char *digits;
|
||||
|
||||
if (!rsession->tech_pvt) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
channel = switch_core_session_get_channel(rsession->tech_pvt->session);
|
||||
|
||||
if (amf0_is_number(argv[2])) {
|
||||
dtmf.duration = amf0_get_number(argv[2]);
|
||||
} else if (!zstr(amf0_get_string(argv[2]))) {
|
||||
dtmf.duration = atoi(amf0_get_string(argv[2]));
|
||||
}
|
||||
|
||||
if ((digits = amf0_get_string(argv[1]))) {
|
||||
size_t len = strlen(digits);
|
||||
size_t j;
|
||||
for (j = 0; j < len; j++) {
|
||||
dtmf.digit = digits[j];
|
||||
switch_channel_queue_dtmf(channel, &dtmf);
|
||||
}
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_login)
|
||||
{
|
||||
char *user, *auth, *domain, *ddomain = NULL;
|
||||
|
||||
|
||||
user = amf0_get_string(argv[1]);
|
||||
auth = amf0_get_string(argv[2]);
|
||||
|
||||
if (zstr(user) || zstr(auth)) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if ((domain = strchr(user, '@'))) {
|
||||
*domain++ = '\0';
|
||||
}
|
||||
|
||||
if (zstr(domain)) {
|
||||
ddomain = switch_core_get_variable_dup("domain");
|
||||
domain = ddomain;
|
||||
}
|
||||
|
||||
|
||||
if (rtmp_check_auth(rsession, user, domain, auth) == SWITCH_STATUS_SUCCESS) {
|
||||
rtmp_session_login(rsession, user, domain);
|
||||
} else {
|
||||
rtmp_send_invoke_free(rsession, 3, 0, 0,
|
||||
amf0_str("onLogin"),
|
||||
amf0_number_new(0),
|
||||
amf0_null_new(),
|
||||
amf0_str("failure"),
|
||||
amf0_null_new(),
|
||||
amf0_null_new(), NULL);
|
||||
}
|
||||
|
||||
|
||||
switch_safe_free(ddomain);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_logout)
|
||||
{
|
||||
char *auth = amf0_get_string(argv[1]);
|
||||
char *user = NULL, *domain = NULL;
|
||||
|
||||
/* Unregister from that user */
|
||||
rtmp_clear_registration(rsession, auth, NULL);
|
||||
|
||||
switch_split_user_domain(auth, &user, &domain);
|
||||
|
||||
if (!zstr(user) && !zstr(domain)) {
|
||||
rtmp_session_logout(rsession, user, domain);
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_register)
|
||||
{
|
||||
char *auth = amf0_get_string(argv[1]);
|
||||
const char *user = NULL, *domain = NULL;
|
||||
char *dup = NULL;
|
||||
switch_status_t status;
|
||||
|
||||
if (!rsession->account) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (!zstr(auth)) {
|
||||
dup = strdup(auth);
|
||||
switch_split_user_domain(dup, (char**)&user, (char**)&domain);
|
||||
} else {
|
||||
dup = auth = switch_mprintf("%s@%s", rsession->account->user, rsession->account->domain);
|
||||
user = rsession->account->user;
|
||||
domain = rsession->account->domain;
|
||||
}
|
||||
|
||||
if (rtmp_session_check_user(rsession, user, domain) == SWITCH_STATUS_SUCCESS) {
|
||||
rtmp_add_registration(rsession, auth, amf0_get_string(argv[2]));
|
||||
status = SWITCH_STATUS_SUCCESS;
|
||||
} else {
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
switch_safe_free(dup);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_unregister)
|
||||
{
|
||||
rtmp_clear_registration(rsession, amf0_get_string(argv[1]), amf0_get_string(argv[2]));
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_answer)
|
||||
{
|
||||
switch_channel_t *channel = NULL;
|
||||
char *uuid = amf0_get_string(argv[1]);
|
||||
|
||||
if (!zstr(uuid)) {
|
||||
rtmp_private_t *new_tech_pvt = rtmp_locate_private(rsession, uuid);
|
||||
if (new_tech_pvt) {
|
||||
switch_channel_mark_answered(switch_core_session_get_channel(new_tech_pvt->session));
|
||||
rtmp_attach_private(rsession, new_tech_pvt);
|
||||
}
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (!rsession->tech_pvt) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* No UUID specified but we're attached to a channel, mark it as answered */
|
||||
channel = switch_core_session_get_channel(rsession->tech_pvt->session);
|
||||
switch_channel_mark_answered(channel);
|
||||
rtmp_attach_private(rsession, rsession->tech_pvt);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_attach)
|
||||
{
|
||||
rtmp_private_t *tech_pvt = NULL;
|
||||
char *uuid = amf0_get_string(argv[1]);
|
||||
|
||||
if (!zstr(uuid)) {
|
||||
tech_pvt = rtmp_locate_private(rsession, uuid);
|
||||
}
|
||||
/* Will detach if an empty (or invalid) uuid is received */
|
||||
rtmp_attach_private(rsession, tech_pvt);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_hangup)
|
||||
{
|
||||
/* CallID (or null/nothing to hangup the current call) */
|
||||
char *uuid = amf0_get_string(argv[1]);
|
||||
char *scause;
|
||||
switch_channel_t *channel = NULL;
|
||||
switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;
|
||||
|
||||
if (!zstr(uuid)) {
|
||||
rtmp_private_t *tech_pvt = rtmp_locate_private(rsession, uuid);
|
||||
if (tech_pvt) {
|
||||
channel = switch_core_session_get_channel(tech_pvt->session);
|
||||
}
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
if (!rsession->tech_pvt) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
channel = switch_core_session_get_channel(rsession->tech_pvt->session);
|
||||
}
|
||||
|
||||
if (amf0_is_number(argv[2])) {
|
||||
cause = amf0_get_number(argv[2]);
|
||||
} else if ((scause = amf0_get_string(argv[2])) && !zstr(scause)) {
|
||||
cause = switch_channel_str2cause(scause);
|
||||
}
|
||||
|
||||
switch_channel_hangup(channel, cause);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_transfer)
|
||||
{
|
||||
char *uuid = amf0_get_string(argv[1]);
|
||||
char *dest = amf0_get_string(argv[2]);
|
||||
rtmp_private_t *tech_pvt;
|
||||
|
||||
if (zstr(uuid) || zstr(dest)) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if ((tech_pvt = rtmp_locate_private(rsession, uuid))) {
|
||||
const char *other_uuid = switch_channel_get_variable(tech_pvt->channel, SWITCH_SIGNAL_BOND_VARIABLE);
|
||||
switch_core_session_t *session;
|
||||
|
||||
if (!zstr(other_uuid) && (session = switch_core_session_locate(other_uuid))) {
|
||||
switch_ivr_session_transfer(session, dest, NULL, NULL);
|
||||
switch_core_session_rwunlock(session);
|
||||
}
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_join)
|
||||
{
|
||||
char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) };
|
||||
const char *other_uuid[2];
|
||||
rtmp_private_t *tech_pvt[2];
|
||||
|
||||
if (zstr(uuid[0]) || zstr(uuid[1])) {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (!(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) ||
|
||||
!(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (tech_pvt[0] == tech_pvt[1]) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if ((other_uuid[0] = switch_channel_get_variable(tech_pvt[0]->channel, SWITCH_SIGNAL_BOND_VARIABLE)) &&
|
||||
(other_uuid[1] = switch_channel_get_variable(tech_pvt[1]->channel, SWITCH_SIGNAL_BOND_VARIABLE))) {
|
||||
|
||||
#ifndef RTMP_DONT_HOLD
|
||||
if (switch_test_flag(tech_pvt[0], TFLAG_DETACHED)) {
|
||||
switch_ivr_unhold(tech_pvt[0]->session);
|
||||
}
|
||||
if (switch_test_flag(tech_pvt[1], TFLAG_DETACHED)) {
|
||||
switch_ivr_unhold(tech_pvt[1]->session);
|
||||
}
|
||||
#endif
|
||||
|
||||
switch_ivr_uuid_bridge(other_uuid[0], other_uuid[1]);
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
3-way:
|
||||
|
||||
[0] is always the current active call
|
||||
[1] is the call to be brought into the call, and that will be running the three_way application
|
||||
|
||||
- Set the current app of other[1] to three_way with other_uuid[0]
|
||||
- Put tech_pvt[0] to sleep: set state to CS_HIBERNATE
|
||||
- set CF_TRANSFER, set state CS_EXECUTE (do we need CF_TRANSFER here?)
|
||||
|
||||
- setup a state handler in other[1] to detect when it hangs up
|
||||
|
||||
Check list:
|
||||
tech_pvt[0] or other[0] hangs up
|
||||
If we were attached to the call, switch the active call to tech_pvt[1]
|
||||
tech_pvt[1] or other[1] hangs up
|
||||
Clear up any 3-way indications on the tech_pvt[0]
|
||||
|
||||
*/
|
||||
|
||||
static switch_status_t three_way_on_soft_execute(switch_core_session_t *session);
|
||||
#if 0
|
||||
static switch_status_t three_way_on_hangup(switch_core_session_t *session);
|
||||
#endif
|
||||
|
||||
static const switch_state_handler_table_t three_way_state_handlers_remote = {
|
||||
/*.on_init */ NULL,
|
||||
/*.on_routing */ NULL,
|
||||
/*.on_execute */ NULL,
|
||||
/*.on_hangup */ NULL,
|
||||
/*.on_exchange_media */ NULL,
|
||||
/*.on_soft_execute */ three_way_on_soft_execute,
|
||||
/*.on_consume_media */ NULL,
|
||||
/*.on_hibernate */ NULL
|
||||
};
|
||||
|
||||
/* runs on other_session[1] */
|
||||
static switch_status_t three_way_on_soft_execute(switch_core_session_t *other_session)
|
||||
{
|
||||
switch_channel_t *other_channel = switch_core_session_get_channel(other_session);
|
||||
const char *uuid = switch_channel_get_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE);
|
||||
const char *my_uuid = switch_channel_get_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE);
|
||||
switch_core_session_t *my_session;
|
||||
switch_channel_t *my_channel;
|
||||
rtmp_private_t *tech_pvt;
|
||||
|
||||
if (zstr(uuid) || zstr(my_uuid)) {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (zstr(my_uuid) || !(my_session = switch_core_session_locate(my_uuid))) {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (!switch_core_session_check_interface(my_session, rtmp_globals.rtmp_endpoint_interface)) {
|
||||
/* In case someone tempers with my variables, since we get tech_pvt from there */
|
||||
switch_core_session_rwunlock(my_session);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
my_channel = switch_core_session_get_channel(my_session);
|
||||
tech_pvt = switch_core_session_get_private(my_session);
|
||||
|
||||
switch_ivr_eavesdrop_session(other_session, uuid, NULL, ED_MUX_READ | ED_MUX_WRITE);
|
||||
|
||||
/* 3-way call ended, whatever the reason
|
||||
* We need to go back to our original state. */
|
||||
if (!switch_channel_up(other_channel)) {
|
||||
/* channel[1] hung up, check if we have special post-bridge actions, and hangup otherwise */
|
||||
/* if my_channel isn't ready, it means something else has control of it, leave it alone */
|
||||
if (switch_channel_ready(my_channel)) {
|
||||
const char *s;
|
||||
if ((s = switch_channel_get_variable(my_channel, SWITCH_PARK_AFTER_BRIDGE_VARIABLE)) && switch_true(s)) {
|
||||
switch_ivr_park_session(my_session);
|
||||
} else if ((s = switch_channel_get_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE)) && !zstr(s)) {
|
||||
int argc;
|
||||
char *argv[4] = { 0 };
|
||||
char *mydata = switch_core_session_strdup(my_session, s);
|
||||
|
||||
switch_channel_set_variable(my_channel, SWITCH_TRANSFER_AFTER_BRIDGE_VARIABLE, NULL);
|
||||
|
||||
if ((argc = switch_split(mydata, ':', argv)) >= 1) {
|
||||
switch_ivr_session_transfer(my_session, argv[0], argv[1], argv[2]);
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(my_session), SWITCH_LOG_ERROR, "No extension specified.\n");
|
||||
}
|
||||
} else {
|
||||
switch_channel_hangup(my_channel, SWITCH_CAUSE_NORMAL_CLEARING);
|
||||
}
|
||||
}
|
||||
} else if (switch_channel_ready(other_channel)) {
|
||||
/* channel[1] didn't hangup, must be channel[0] then, rebridge this one with its original partner */
|
||||
switch_ivr_uuid_bridge(switch_core_session_get_uuid(other_session), my_uuid);
|
||||
} else {
|
||||
/* channel[1] being taken out of our control, take the other leg out of CS_HIBERNATE if its ready, or else leave it alone */
|
||||
if (switch_channel_ready(my_channel)) {
|
||||
switch_channel_set_state(my_channel, CS_EXECUTE);
|
||||
}
|
||||
}
|
||||
|
||||
switch_channel_clear_state_handler(other_channel, &three_way_state_handlers_remote);
|
||||
|
||||
switch_channel_set_variable(other_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL);
|
||||
switch_channel_set_variable(my_channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, NULL);
|
||||
switch_channel_set_variable(other_channel, RTMP_THREE_WAY_UUID_VARIABLE, NULL);
|
||||
|
||||
switch_clear_flag(tech_pvt, TFLAG_THREE_WAY);
|
||||
|
||||
if (my_session) {
|
||||
switch_core_session_rwunlock(my_session);
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_three_way)
|
||||
{
|
||||
/* The first uuid is the (local) uuid of the current call, the 2nd one should be already detached */
|
||||
char *uuid[] = { amf0_get_string(argv[1]), amf0_get_string(argv[2]) };
|
||||
rtmp_private_t *tech_pvt[2];
|
||||
const char *other_uuid[2];
|
||||
switch_core_session_t *other_session[2] = { 0 };
|
||||
switch_channel_t *other_channel[2] = { 0 };
|
||||
|
||||
if (zstr(uuid[0]) || zstr(uuid[1]) ||
|
||||
!(tech_pvt[0] = rtmp_locate_private(rsession, uuid[0])) ||
|
||||
!(tech_pvt[1] = rtmp_locate_private(rsession, uuid[1]))) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* Make sure we don't 3-way with the same call, and that it doesnt turn into a 4-way, we aren't that permissive */
|
||||
if (tech_pvt[0] == tech_pvt[1] || switch_test_flag(tech_pvt[0], TFLAG_THREE_WAY) ||
|
||||
switch_test_flag(tech_pvt[1], TFLAG_THREE_WAY)) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (!(other_uuid[0] = switch_channel_get_variable(tech_pvt[0]->channel, SWITCH_SIGNAL_BOND_VARIABLE)) ||
|
||||
!(other_uuid[1] = switch_channel_get_variable(tech_pvt[1]->channel, SWITCH_SIGNAL_BOND_VARIABLE))) {
|
||||
return SWITCH_STATUS_FALSE; /* Both calls aren't bridged */
|
||||
}
|
||||
|
||||
if (!(other_session[0] = switch_core_session_locate(other_uuid[0])) ||
|
||||
!(other_session[1] = switch_core_session_locate(other_uuid[1]))) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
other_channel[0] = switch_core_session_get_channel(other_session[0]);
|
||||
other_channel[1] = switch_core_session_get_channel(other_session[1]);
|
||||
|
||||
/* Save which uuid is the 3-way target */
|
||||
switch_channel_set_variable(other_channel[1], RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]);
|
||||
switch_channel_set_variable(tech_pvt[1]->channel, RTMP_THREE_WAY_UUID_VARIABLE, uuid[0]);
|
||||
|
||||
/* Attach redirect */
|
||||
switch_set_flag(tech_pvt[1], TFLAG_THREE_WAY);
|
||||
|
||||
/* Set soft_holding_uuid to the uuid of the other matching channel, so they can can be bridged back when the 3-way is over */
|
||||
switch_channel_set_variable(tech_pvt[1]->channel, SWITCH_SOFT_HOLDING_UUID_VARIABLE, other_uuid[1]);
|
||||
switch_channel_set_variable(other_channel[1], SWITCH_SOFT_HOLDING_UUID_VARIABLE, uuid[1]);
|
||||
|
||||
/* Start the 3-way on the 2nd channel using a media bug */
|
||||
switch_channel_add_state_handler(other_channel[1], &three_way_state_handlers_remote);
|
||||
|
||||
switch_channel_set_flag(tech_pvt[1]->channel, CF_TRANSFER);
|
||||
switch_channel_set_state(tech_pvt[1]->channel, CS_HIBERNATE);
|
||||
switch_channel_set_flag(other_channel[1], CF_TRANSFER);
|
||||
switch_channel_set_state(other_channel[1], CS_SOFT_EXECUTE);
|
||||
|
||||
done:
|
||||
|
||||
if (other_session[0]) {
|
||||
switch_core_session_rwunlock(other_session[0]);
|
||||
}
|
||||
|
||||
if (other_session[1]) {
|
||||
switch_core_session_rwunlock(other_session[1]);
|
||||
}
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_sendevent)
|
||||
{
|
||||
amf0_data *obj = NULL;
|
||||
switch_event_t *event = NULL;
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
const char *uuid = NULL;
|
||||
|
||||
if (argv[1] && argv[1]->type == AMF0_TYPE_OBJECT) {
|
||||
obj = argv[1];
|
||||
} else if (argv[2] && argv[2]->type == AMF0_TYPE_OBJECT) {
|
||||
uuid = amf0_get_string(argv[1]);
|
||||
obj = argv[2];
|
||||
} else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Bad argument for sendevent");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
|
||||
if (switch_event_create_subclass(&event, zstr(uuid) ? SWITCH_EVENT_CUSTOM : SWITCH_EVENT_MESSAGE,
|
||||
zstr(uuid) ? RTMP_EVENT_CLIENTCUSTOM : NULL) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_ERROR, "Couldn't create event\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
rtmp_event_fill(rsession, event);
|
||||
|
||||
/* Build event using amf array */
|
||||
if ((status = amf_object_to_event(obj, &event)) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_event_destroy(&event);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (!zstr(uuid)) {
|
||||
rtmp_private_t *session_pvt = rtmp_locate_private(rsession, uuid);
|
||||
if (session_pvt) {
|
||||
if (switch_core_session_queue_event(session_pvt->session, &event) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_pvt->session), SWITCH_LOG_ERROR, "Couldn't queue event to session\n");
|
||||
switch_event_destroy(&event);
|
||||
status = SWITCH_STATUS_FALSE;
|
||||
} else {
|
||||
status = SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch_event_fire(&event);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
RTMP_INVOKE_FUNCTION(rtmp_i_log)
|
||||
{
|
||||
const char *data = amf0_get_string(argv[1]);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_INFO, "Log: %s\n", data);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* For Emacs:
|
||||
* Local Variables:
|
||||
* mode:c
|
||||
* indent-tabs-mode:t
|
||||
* tab-width:4
|
||||
* c-basic-offset:4
|
||||
* End:
|
||||
* For VIM:
|
||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
|
||||
*/
|
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
* Copyright (C) 2011, Barracuda Networks Inc.
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mod_rtmp for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||
*
|
||||
* The Initial Developer of the Original Code is Barracuda Networks Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Mathieu Rene <mrene@avgs.ca>
|
||||
*
|
||||
* rtmp_tcp.c -- RTMP TCP I/O module
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mod_rtmp.h"
|
||||
|
||||
/* Locally-extended version of rtmp_io_t */
|
||||
struct rtmp_io_tcp {
|
||||
rtmp_io_t base;
|
||||
|
||||
switch_pollset_t *pollset;
|
||||
switch_pollfd_t *listen_pollfd;
|
||||
switch_socket_t *listen_socket;
|
||||
const char *ip;
|
||||
switch_port_t port;
|
||||
switch_thread_t *thread;
|
||||
switch_mutex_t *mutex;
|
||||
};
|
||||
|
||||
typedef struct rtmp_io_tcp rtmp_io_tcp_t;
|
||||
|
||||
struct rtmp_tcp_io_private {
|
||||
switch_pollfd_t *pollfd;
|
||||
switch_socket_t *socket;
|
||||
switch_buffer_t *sendq;
|
||||
switch_bool_t poll_send;
|
||||
};
|
||||
|
||||
typedef struct rtmp_tcp_io_private rtmp_tcp_io_private_t;
|
||||
|
||||
static void rtmp_tcp_alter_pollfd(rtmp_session_t *rsession, switch_bool_t pollout)
|
||||
{
|
||||
rtmp_tcp_io_private_t *io_pvt = rsession->io_private;
|
||||
rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io;
|
||||
|
||||
if (pollout && (io_pvt->pollfd->reqevents & SWITCH_POLLOUT)) {
|
||||
return;
|
||||
} else if (!pollout && !(io_pvt->pollfd->reqevents & SWITCH_POLLOUT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch_pollset_remove(io->pollset, io_pvt->pollfd);
|
||||
io_pvt->pollfd->reqevents = SWITCH_POLLIN | SWITCH_POLLERR;
|
||||
if (pollout) {
|
||||
io_pvt->pollfd->reqevents |= SWITCH_POLLOUT;
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Pollout: %s\n",
|
||||
pollout ? "true" : "false");
|
||||
|
||||
switch_pollset_add(io->pollset, io_pvt->pollfd);
|
||||
}
|
||||
|
||||
static switch_status_t rtmp_tcp_read(rtmp_session_t *rsession, unsigned char *buf, switch_size_t *len)
|
||||
{
|
||||
//rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io;
|
||||
rtmp_tcp_io_private_t *io_pvt = rsession->io_private;
|
||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
switch_size_t olen = *len;
|
||||
#endif
|
||||
switch_assert(*len > 0 && *len < 1024000);
|
||||
status = switch_socket_recv(io_pvt->socket, (char*)buf, len);
|
||||
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
{
|
||||
int i;
|
||||
|
||||
fprintf(rsession->io_debug_in, "recv %p max=%"SWITCH_SIZE_T_FMT" got=%"SWITCH_SIZE_T_FMT"\n< ", (void*)buf, olen, *len);
|
||||
|
||||
for (i = 0; i < *len; i++) {
|
||||
|
||||
fprintf(rsession->io_debug_in, "%02X ", (uint8_t)buf[i]);
|
||||
|
||||
if (i != 0 && i % 32 == 0) {
|
||||
fprintf(rsession->io_debug_in, "\n> ");
|
||||
}
|
||||
}
|
||||
fprintf(rsession->io_debug_in, "\n\n");
|
||||
fflush(rsession->io_debug_in);
|
||||
}
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static switch_status_t rtmp_tcp_write(rtmp_session_t *rsession, const unsigned char *buf, switch_size_t *len)
|
||||
{
|
||||
//rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io;
|
||||
rtmp_tcp_io_private_t *io_pvt = rsession->io_private;
|
||||
switch_status_t status;
|
||||
switch_size_t orig_len = *len;
|
||||
|
||||
#ifdef RTMP_DEBUG_IO
|
||||
{
|
||||
int i;
|
||||
fprintf(rsession->io_debug_out,
|
||||
"SEND %"SWITCH_SIZE_T_FMT" bytes\n> ", *len);
|
||||
|
||||
for (i = 0; i < *len; i++) {
|
||||
fprintf(rsession->io_debug_out, "%02X ", (uint8_t)buf[i]);
|
||||
|
||||
if (i != 0 && i % 32 == 0) {
|
||||
fprintf(rsession->io_debug_out, "\n> ");
|
||||
}
|
||||
}
|
||||
fprintf(rsession->io_debug_out, "\n\n ");
|
||||
|
||||
fflush(rsession->io_debug_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (io_pvt->sendq && switch_buffer_inuse(io_pvt->sendq) > 0) {
|
||||
/* We already have queued data, append it to the sendq */
|
||||
switch_buffer_write(io_pvt->sendq, buf, *len);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
status = switch_socket_send_nonblock(io_pvt->socket, (char*)buf, len);
|
||||
|
||||
if (*len < orig_len) {
|
||||
|
||||
if (rsession->state >= RS_DESTROY) {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* We didnt send it all... add it to the sendq*/
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%"SWITCH_SIZE_T_FMT" bytes added to sendq.\n", (orig_len - *len));
|
||||
|
||||
switch_buffer_write(io_pvt->sendq, (buf + *len), orig_len - *len);
|
||||
|
||||
/* Make sure we poll-write */
|
||||
rtmp_tcp_alter_pollfd(rsession, SWITCH_TRUE);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static switch_status_t rtmp_tcp_close(rtmp_session_t *rsession)
|
||||
{
|
||||
rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)rsession->profile->io;
|
||||
rtmp_tcp_io_private_t *io_pvt = rsession->io_private;
|
||||
|
||||
if (io_pvt->socket) {
|
||||
switch_mutex_lock(io->mutex);
|
||||
switch_pollset_remove(io->pollset, io_pvt->pollfd);
|
||||
switch_mutex_unlock(io->mutex);
|
||||
|
||||
switch_socket_close(io_pvt->socket);
|
||||
io_pvt->socket = NULL;
|
||||
}
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void *SWITCH_THREAD_FUNC rtmp_io_tcp_thread(switch_thread_t *thread, void *obj)
|
||||
{
|
||||
rtmp_io_tcp_t *io = (rtmp_io_tcp_t*)obj;
|
||||
io->base.running = 1;
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s: I/O Thread starting\n", io->base.profile->name);
|
||||
|
||||
|
||||
while(io->base.running) {
|
||||
const switch_pollfd_t *fds;
|
||||
int32_t numfds;
|
||||
int32_t i;
|
||||
switch_status_t status;
|
||||
|
||||
switch_mutex_lock(io->mutex);
|
||||
status = switch_pollset_poll(io->pollset, 500000, &numfds, &fds);
|
||||
switch_mutex_unlock(io->mutex);
|
||||
|
||||
if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_TIMEOUT) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "pollset_poll failed\n");
|
||||
continue;
|
||||
} else if (status == SWITCH_STATUS_TIMEOUT) {
|
||||
switch_yield(1);
|
||||
}
|
||||
|
||||
for (i = 0; i < numfds; i++) {
|
||||
if (!fds[i].client_data) {
|
||||
switch_socket_t *newsocket;
|
||||
if (switch_socket_accept(&newsocket, io->listen_socket, io->base.pool) != SWITCH_STATUS_SUCCESS) {
|
||||
if (io->base.running) {
|
||||
/* Don't spam the logs if we are shutting down */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Socket Error [%s]\n", strerror(errno));
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
rtmp_session_t *newsession;
|
||||
if (rtmp_session_request(io->base.profile, &newsession) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "RTMP session request failed\n");
|
||||
switch_socket_close(newsocket);
|
||||
} else {
|
||||
switch_sockaddr_t *addr = NULL;
|
||||
char ipbuf[200];
|
||||
|
||||
/* Create out private data and attach it to the rtmp session structure */
|
||||
rtmp_tcp_io_private_t *pvt = switch_core_alloc(newsession->pool, sizeof(*pvt));
|
||||
newsession->io_private = pvt;
|
||||
pvt->socket = newsocket;
|
||||
switch_socket_create_pollfd(&pvt->pollfd, newsocket, SWITCH_POLLIN | SWITCH_POLLERR, newsession, newsession->pool);
|
||||
switch_pollset_add(io->pollset, pvt->pollfd);
|
||||
switch_buffer_create_dynamic(&pvt->sendq, 512, 1024, 0);
|
||||
|
||||
/* Get the remote address/port info */
|
||||
switch_socket_addr_get(&addr, SWITCH_TRUE, newsocket);
|
||||
switch_get_addr(ipbuf, sizeof(ipbuf), addr);
|
||||
newsession->remote_address = switch_core_strdup(newsession->pool, ipbuf);
|
||||
newsession->remote_port = switch_sockaddr_get_port(addr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rtmp_session_t *rsession = (rtmp_session_t*)fds[i].client_data;
|
||||
rtmp_tcp_io_private_t *io_pvt = (rtmp_tcp_io_private_t*)rsession->io_private;
|
||||
|
||||
if (fds[i].rtnevents & SWITCH_POLLOUT && switch_buffer_inuse(io_pvt->sendq) > 0) {
|
||||
/* Send as much remaining data as possible */
|
||||
switch_size_t sendlen;
|
||||
const void *ptr;
|
||||
sendlen = switch_buffer_peek_zerocopy(io_pvt->sendq, &ptr);
|
||||
switch_socket_send_nonblock(io_pvt->socket, ptr, &sendlen);
|
||||
switch_buffer_toss(io_pvt->sendq, sendlen);
|
||||
if (switch_buffer_inuse(io_pvt->sendq) == 0) {
|
||||
/* Remove our fd from OUT polling */
|
||||
rtmp_tcp_alter_pollfd(rsession, SWITCH_FALSE);
|
||||
}
|
||||
} else if (fds[i].rtnevents & SWITCH_POLLIN && rtmp_handle_data(rsession) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(rsession->uuid), SWITCH_LOG_DEBUG, "Closing socket\n");
|
||||
|
||||
switch_mutex_lock(io->mutex);
|
||||
switch_pollset_remove(io->pollset, io_pvt->pollfd);
|
||||
switch_mutex_unlock(io->mutex);
|
||||
|
||||
switch_socket_close(io_pvt->socket);
|
||||
io_pvt->socket = NULL;
|
||||
|
||||
rtmp_session_destroy(&rsession);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
io->base.running = -1;
|
||||
switch_socket_close(io->listen_socket);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch_status_t rtmp_tcp_init(rtmp_profile_t *profile, const char *bindaddr, rtmp_io_t **new_io, switch_memory_pool_t *pool)
|
||||
{
|
||||
char *szport;
|
||||
switch_sockaddr_t *sa;
|
||||
switch_threadattr_t *thd_attr = NULL;
|
||||
rtmp_io_tcp_t *io_tcp;
|
||||
|
||||
io_tcp = (rtmp_io_tcp_t*)switch_core_alloc(pool, sizeof(rtmp_io_tcp_t));
|
||||
io_tcp->base.pool = pool;
|
||||
io_tcp->ip = switch_core_strdup(pool, bindaddr);
|
||||
|
||||
*new_io = (rtmp_io_t*)io_tcp;
|
||||
io_tcp->base.profile = profile;
|
||||
io_tcp->base.read = rtmp_tcp_read;
|
||||
io_tcp->base.write = rtmp_tcp_write;
|
||||
io_tcp->base.close = rtmp_tcp_close;
|
||||
io_tcp->base.name = "tcp";
|
||||
io_tcp->base.address = switch_core_strdup(pool, io_tcp->ip);
|
||||
|
||||
if ((szport = strchr(io_tcp->ip, ':'))) {
|
||||
*szport++ = '\0';
|
||||
io_tcp->port = atoi(szport);
|
||||
} else {
|
||||
io_tcp->port = RTMP_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
if (switch_sockaddr_info_get(&sa, io_tcp->ip, SWITCH_INET, io_tcp->port, 0, pool)) {
|
||||
goto fail;
|
||||
}
|
||||
if (switch_socket_create(&io_tcp->listen_socket, switch_sockaddr_get_family(sa), SOCK_STREAM, SWITCH_PROTO_TCP, pool)) {
|
||||
goto fail;
|
||||
}
|
||||
if (switch_socket_opt_set(io_tcp->listen_socket, SWITCH_SO_REUSEADDR, 1)) {
|
||||
goto fail;
|
||||
}
|
||||
if (switch_socket_bind(io_tcp->listen_socket, sa)) {
|
||||
goto fail;
|
||||
}
|
||||
if (switch_socket_listen(io_tcp->listen_socket, 10)) {
|
||||
goto fail;
|
||||
}
|
||||
if (switch_socket_opt_set(io_tcp->listen_socket, SWITCH_SO_NONBLOCK, TRUE)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Listening on %s:%u (tcp)\n", io_tcp->ip, io_tcp->port);
|
||||
|
||||
io_tcp->base.running = 1;
|
||||
|
||||
if (switch_pollset_create(&io_tcp->pollset, 1000 /* max poll fds */, pool, 0) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "pollset_create failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
switch_socket_create_pollfd(&(io_tcp->listen_pollfd), io_tcp->listen_socket, SWITCH_POLLIN | SWITCH_POLLERR, NULL, pool);
|
||||
if (switch_pollset_add(io_tcp->pollset, io_tcp->listen_pollfd) != SWITCH_STATUS_SUCCESS) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "pollset_add failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
switch_mutex_init(&io_tcp->mutex, SWITCH_MUTEX_NESTED, pool);
|
||||
|
||||
switch_threadattr_create(&thd_attr, pool);
|
||||
switch_threadattr_detach_set(thd_attr, 1);
|
||||
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
|
||||
switch_thread_create(&io_tcp->thread, thd_attr, rtmp_io_tcp_thread, *new_io, pool);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
fail:
|
||||
if (io_tcp->listen_socket) {
|
||||
switch_socket_close(io_tcp->listen_socket);
|
||||
}
|
||||
*new_io = NULL;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Socket error. Couldn't listen on %s:%u\n", io_tcp->ip, io_tcp->port);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* For Emacs:
|
||||
* Local Variables:
|
||||
* mode:c
|
||||
* indent-tabs-mode:t
|
||||
* tab-width:4
|
||||
* c-basic-offset:4
|
||||
* End:
|
||||
* For VIM:
|
||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
|
||||
*/
|
Loading…
Reference in New Issue