2011-04-20 20:52:15 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2011, Digium, Inc.
*
* David Vossel <dvossel@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief Put a jitterbuffer on the read side of a channel
*
* \author David Vossel <dvossel@digium.com>
*
* \ingroup functions
*/
2011-10-14 18:38:08 +00:00
/*** MODULEINFO
<support_level>core</support_level>
***/
2011-04-20 20:52:15 +00:00
#include "asterisk.h"
ASTERISK_FILE_VERSION ( __FILE__ , "$Revision$" )
#include "asterisk/module.h"
#include "asterisk/channel.h"
#include "asterisk/framehook.h"
#include "asterisk/pbx.h"
#include "asterisk/abstract_jb.h"
#include "asterisk/timing.h"
#include "asterisk/app.h"
/*** DOCUMENTATION
<function name="JITTERBUFFER" language="en_US">
<synopsis>
Add a Jitterbuffer to the Read side of the channel. This dejitters the audio stream before it reaches the Asterisk core. This is a write only function.
</synopsis>
<syntax>
<parameter name="jitterbuffer type" required="true">
<para>Jitterbuffer type can be either <literal>fixed</literal> or <literal>adaptive</literal>.</para>
<para>Used as follows. </para>
<para>Set(JITTERBUFFER(type)=max_size[,resync_threshold[,target_extra]])</para>
<para>Set(JITTERBUFFER(type)=default) </para>
</parameter>
</syntax>
<description>
<para>max_size: Defaults to 200 ms</para>
<para>Length in milliseconds of buffer.</para>
<para> </para>
<para>resync_threshold: Defaults to 1000ms </para>
<para>The length in milliseconds over which a timestamp difference will result in resyncing the jitterbuffer. </para>
<para> </para>
<para>target_extra: Defaults to 40ms</para>
<para>This option only affects the adaptive jitterbuffer. It represents the amount time in milliseconds by which the new jitter buffer will pad its size.</para>
<para> </para>
<para>Examples:</para>
<para>exten => 1,1,Set(JITTERBUFFER(fixed)=default);Fixed with defaults. </para>
<para>exten => 1,1,Set(JITTERBUFFER(fixed)=200);Fixed with max size 200ms, default resync threshold and target extra. </para>
<para>exten => 1,1,Set(JITTERBUFFER(fixed)=200,1500);Fixed with max size 200ms resync threshold 1500. </para>
<para>exten => 1,1,Set(JITTERBUFFER(adaptive)=default);Adaptive with defaults. </para>
<para>exten => 1,1,Set(JITTERBUFFER(adaptive)=200,,60);Adaptive with max size 200ms, default resync threshold and 40ms target extra. </para>
</description>
</function>
***/
#define DEFAULT_TIMER_INTERVAL 20
#define DEFAULT_SIZE 200
#define DEFAULT_TARGET_EXTRA 40
#define DEFAULT_RESYNC 1000
#define DEFAULT_TYPE AST_JB_FIXED
struct jb_framedata {
const struct ast_jb_impl * jb_impl ;
struct ast_jb_conf jb_conf ;
struct timeval start_tv ;
struct ast_format last_format ;
struct ast_timer * timer ;
int timer_interval ; /* ms between deliveries */
int timer_fd ;
int first ;
void * jb_obj ;
};
static void jb_framedata_destroy ( struct jb_framedata * framedata )
{
if ( framedata -> timer ) {
ast_timer_close ( framedata -> timer );
framedata -> timer = NULL ;
}
if ( framedata -> jb_impl && framedata -> jb_obj ) {
struct ast_frame * f ;
while ( framedata -> jb_impl -> remove ( framedata -> jb_obj , & f ) == AST_JB_IMPL_OK ) {
ast_frfree ( f );
}
framedata -> jb_impl -> destroy ( framedata -> jb_obj );
framedata -> jb_obj = NULL ;
}
ast_free ( framedata );
}
static void jb_conf_default ( struct ast_jb_conf * conf )
{
conf -> max_size = DEFAULT_SIZE ;
conf -> resync_threshold = DEFAULT_RESYNC ;
ast_copy_string ( conf -> impl , "fixed" , sizeof ( conf -> impl ));
conf -> target_extra = DEFAULT_TARGET_EXTRA ;
}
/* set defaults */
static int jb_framedata_init ( struct jb_framedata * framedata , const char * data , const char * value )
{
int jb_impl_type = DEFAULT_TYPE ;
/* Initialize defaults */
framedata -> timer_fd = - 1 ;
jb_conf_default ( & framedata -> jb_conf );
if ( ! ( framedata -> jb_impl = ast_jb_get_impl ( jb_impl_type ))) {
return - 1 ;
}
if ( ! ( framedata -> timer = ast_timer_open ())) {
return - 1 ;
}
framedata -> timer_fd = ast_timer_fd ( framedata -> timer );
framedata -> timer_interval = DEFAULT_TIMER_INTERVAL ;
ast_timer_set_rate ( framedata -> timer , 1000 / framedata -> timer_interval );
framedata -> start_tv = ast_tvnow ();
/* Now check user options to see if any of the defaults need to change. */
if ( ! ast_strlen_zero ( data )) {
if ( ! strcasecmp ( data , "fixed" )) {
jb_impl_type = AST_JB_FIXED ;
} else if ( ! strcasecmp ( data , "adaptive" )) {
jb_impl_type = AST_JB_ADAPTIVE ;
} else {
ast_log ( LOG_WARNING , "Unknown Jitterbuffer type %s. Failed to create jitterbuffer. \n " , data );
return - 1 ;
}
ast_copy_string ( framedata -> jb_conf . impl , data , sizeof ( framedata -> jb_conf . impl ));
}
if ( ! ast_strlen_zero ( value ) && strcasecmp ( value , "default" )) {
char * parse = ast_strdupa ( value );
int res = 0 ;
AST_DECLARE_APP_ARGS ( args ,
AST_APP_ARG ( max_size );
AST_APP_ARG ( resync_threshold );
AST_APP_ARG ( target_extra );
);
AST_STANDARD_APP_ARGS ( args , parse );
if ( ! ast_strlen_zero ( args . max_size )) {
res |= ast_jb_read_conf ( & framedata -> jb_conf ,
"jbmaxsize" ,
args . max_size );
}
if ( ! ast_strlen_zero ( args . resync_threshold )) {
res |= ast_jb_read_conf ( & framedata -> jb_conf ,
"jbresyncthreshold" ,
args . resync_threshold );
}
if ( ! ast_strlen_zero ( args . target_extra )) {
res |= ast_jb_read_conf ( & framedata -> jb_conf ,
"jbtargetextra" ,
args . target_extra );
}
if ( res ) {
ast_log ( LOG_WARNING , "Invalid jitterbuffer parameters %s \n " , value );
}
}
/* now that all the user parsing is done and nothing will change, create the jb obj */
2012-07-23 21:15:26 +00:00
framedata -> jb_obj = framedata -> jb_impl -> create ( & framedata -> jb_conf );
2011-04-20 20:52:15 +00:00
return 0 ;
}
static void datastore_destroy_cb ( void * data ) {
ast_free ( data );
ast_debug ( 1 , "JITTERBUFFER datastore destroyed \n " );
}
static const struct ast_datastore_info jb_datastore = {
. type = "jitterbuffer" ,
. destroy = datastore_destroy_cb
};
static void hook_destroy_cb ( void * framedata )
{
ast_debug ( 1 , "JITTERBUFFER hook destroyed \n " );
jb_framedata_destroy (( struct jb_framedata * ) framedata );
}
static struct ast_frame * hook_event_cb ( struct ast_channel * chan , struct ast_frame * frame , enum ast_framehook_event event , void * data )
{
struct jb_framedata * framedata = data ;
struct timeval now_tv ;
unsigned long now ;
2011-05-05 18:08:42 +00:00
int putframe = 0 ; /* signifies if audio frame was placed into the buffer or not */
2011-04-20 20:52:15 +00:00
switch ( event ) {
case AST_FRAMEHOOK_EVENT_READ :
break ;
case AST_FRAMEHOOK_EVENT_ATTACHED :
case AST_FRAMEHOOK_EVENT_DETACHED :
case AST_FRAMEHOOK_EVENT_WRITE :
return frame ;
}
2012-02-20 23:43:27 +00:00
if ( ast_channel_fdno ( chan ) == AST_JITTERBUFFER_FD && framedata -> timer ) {
2011-04-20 20:52:15 +00:00
ast_timer_ack ( framedata -> timer , 1 );
}
if ( ! frame ) {
return frame ;
}
now_tv = ast_tvnow ();
now = ast_tvdiff_ms ( now_tv , framedata -> start_tv );
if ( frame -> frametype == AST_FRAME_VOICE ) {
int res ;
struct ast_frame * jbframe ;
if ( ! ast_test_flag ( frame , AST_FRFLAG_HAS_TIMING_INFO ) || frame -> len < 2 || frame -> ts < 0 ) {
/* only frames with timing info can enter the jitterbuffer */
return frame ;
}
jbframe = ast_frisolate ( frame );
ast_format_copy ( & framedata -> last_format , & frame -> subclass . format );
if ( frame -> len && ( frame -> len != framedata -> timer_interval )) {
framedata -> timer_interval = frame -> len ;
ast_timer_set_rate ( framedata -> timer , 1000 / framedata -> timer_interval );
}
if ( ! framedata -> first ) {
framedata -> first = 1 ;
res = framedata -> jb_impl -> put_first ( framedata -> jb_obj , jbframe , now );
} else {
res = framedata -> jb_impl -> put ( framedata -> jb_obj , jbframe , now );
}
if ( res == AST_JB_IMPL_OK ) {
frame = & ast_null_frame ;
}
2011-05-05 18:08:42 +00:00
putframe = 1 ;
2011-04-20 20:52:15 +00:00
}
if ( frame -> frametype == AST_FRAME_NULL ) {
int res ;
long next = framedata -> jb_impl -> next ( framedata -> jb_obj );
2011-05-05 18:08:42 +00:00
/* If now is earlier than the next expected output frame
* from the jitterbuffer we may choose to pass on retrieving
* a frame during this read iteration. The only exception
* to this rule is when an audio frame is placed into the buffer
* and the time for the next frame to come out of the buffer is
* at least within the timer_interval of the next output frame. By
* doing this we are able to feed off the timing of the input frames
* and only rely on our jitterbuffer timer when frames are dropped.
* During testing, this hybrid form of timing gave more reliable results. */
2011-04-20 20:52:15 +00:00
if ( now < next ) {
2011-05-05 18:08:42 +00:00
long int diff = next - now ;
if ( ! putframe ) {
return frame ;
} else if ( diff >= framedata -> timer_interval ) {
return frame ;
}
2011-04-20 20:52:15 +00:00
}
2011-05-05 18:08:42 +00:00
2011-04-20 20:52:15 +00:00
res = framedata -> jb_impl -> get ( framedata -> jb_obj , & frame , now , framedata -> timer_interval );
switch ( res ) {
case AST_JB_IMPL_OK :
/* got it, and pass it through */
break ;
case AST_JB_IMPL_DROP :
ast_frfree ( frame );
frame = & ast_null_frame ;
break ;
case AST_JB_IMPL_INTERP :
if ( framedata -> last_format . id ) {
struct ast_frame tmp = { 0 , };
tmp . frametype = AST_FRAME_VOICE ;
ast_format_copy ( & tmp . subclass . format , & framedata -> last_format );
/* example: 8000hz / (1000 / 20ms) = 160 samples */
tmp . samples = ast_format_rate ( & framedata -> last_format ) / ( 1000 / framedata -> timer_interval );
tmp . delivery = ast_tvadd ( framedata -> start_tv , ast_samp2tv ( next , 1000 ));
tmp . offset = AST_FRIENDLY_OFFSET ;
tmp . src = "func_jitterbuffer interpolation" ;
frame = ast_frdup ( & tmp );
break ;
}
/* else fall through */
case AST_JB_IMPL_NOFRAME :
frame = & ast_null_frame ;
break ;
}
}
return frame ;
}
static int jb_helper ( struct ast_channel * chan , const char * cmd , char * data , const char * value )
{
struct jb_framedata * framedata ;
struct ast_datastore * datastore = NULL ;
struct ast_framehook_interface interface = {
. version = AST_FRAMEHOOK_INTERFACE_VERSION ,
. event_cb = hook_event_cb ,
. destroy_cb = hook_destroy_cb ,
};
int i = 0 ;
if ( ! ( framedata = ast_calloc ( 1 , sizeof ( * framedata )))) {
return 0 ;
}
if ( jb_framedata_init ( framedata , data , value )) {
jb_framedata_destroy ( framedata );
return 0 ;
}
interface . data = framedata ;
ast_channel_lock ( chan );
i = ast_framehook_attach ( chan , & interface );
if ( i >= 0 ) {
int * id ;
if (( datastore = ast_channel_datastore_find ( chan , & jb_datastore , NULL ))) {
id = datastore -> data ;
ast_framehook_detach ( chan , * id );
ast_channel_datastore_remove ( chan , datastore );
}
if ( ! ( datastore = ast_datastore_alloc ( & jb_datastore , NULL ))) {
ast_framehook_detach ( chan , i );
ast_channel_unlock ( chan );
return 0 ;
}
if ( ! ( id = ast_calloc ( 1 , sizeof ( int )))) {
ast_datastore_free ( datastore );
ast_framehook_detach ( chan , i );
ast_channel_unlock ( chan );
return 0 ;
}
* id = i ; /* Store off the id. The channel is still locked so it is safe to access this ptr. */
datastore -> data = id ;
ast_channel_datastore_add ( chan , datastore );
ast_channel_set_fd ( chan , AST_JITTERBUFFER_FD , framedata -> timer_fd );
} else {
jb_framedata_destroy ( framedata );
framedata = NULL ;
}
ast_channel_unlock ( chan );
return 0 ;
}
static struct ast_custom_function jb_function = {
. name = "JITTERBUFFER" ,
. write = jb_helper ,
};
static int unload_module ( void )
{
return ast_custom_function_unregister ( & jb_function );
}
static int load_module ( void )
{
int res = ast_custom_function_register ( & jb_function );
return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS ;
}
AST_MODULE_INFO_STANDARD ( ASTERISK_GPL_KEY , "Jitter buffer for read side of channel." );