@ -41,6 +41,8 @@
# include "asterisk/utils.h"
# include "asterisk/pbx.h"
# include "asterisk/timing.h"
# include "asterisk/rtp_engine.h"
# include "asterisk/format_cache.h"
# include "asterisk/abstract_jb.h"
# include "fixedjitterbuf.h"
@ -53,6 +55,9 @@ enum {
JB_CREATED = ( 1 < < 2 )
} ;
/*! The maximum size we allow the early frame buffer to get */
# define MAXIMUM_EARLY_FRAME_COUNT 200
/* Implementation functions */
/* fixed */
@ -568,6 +573,8 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
}
} else if ( ! strcasecmp ( name , AST_JB_CONF_LOG ) ) {
ast_set2_flag ( conf , ast_true ( value ) , AST_JB_LOG ) ;
} else if ( ! strcasecmp ( name , AST_JB_CONF_SYNC_VIDEO ) ) {
ast_set2_flag ( conf , ast_true ( value ) , AST_JB_SYNC_VIDEO ) ;
} else {
return - 1 ;
}
@ -832,6 +839,11 @@ static int jb_is_late_adaptive(void *jb, long ts)
# define DEFAULT_RESYNC 1000
# define DEFAULT_TYPE AST_JB_FIXED
struct jb_stream_sync {
unsigned int timestamp ;
struct timeval ntp ;
} ;
struct jb_framedata {
const struct ast_jb_impl * jb_impl ;
struct ast_jb_conf jb_conf ;
@ -841,11 +853,21 @@ struct jb_framedata {
int timer_interval ; /* ms between deliveries */
int timer_fd ;
int first ;
int audio_stream_id ;
struct jb_stream_sync audio_stream_sync ;
int video_stream_id ;
struct jb_stream_sync video_stream_sync ;
AST_LIST_HEAD_NOLOCK ( , ast_frame ) early_frames ;
unsigned int early_frame_count ;
struct timeval last_audio_ntp_timestamp ;
int audio_flowing ;
void * jb_obj ;
} ;
static void jb_framedata_destroy ( struct jb_framedata * framedata )
{
struct ast_frame * frame ;
if ( framedata - > timer ) {
ast_timer_close ( framedata - > timer ) ;
framedata - > timer = NULL ;
@ -859,11 +881,15 @@ static void jb_framedata_destroy(struct jb_framedata *framedata)
framedata - > jb_obj = NULL ;
}
ao2_cleanup ( framedata - > last_format ) ;
while ( ( frame = AST_LIST_REMOVE_HEAD ( & framedata - > early_frames , frame_list ) ) ) {
ast_frfree ( frame ) ;
}
ast_free ( framedata ) ;
}
void ast_jb_conf_default ( struct ast_jb_conf * conf )
{
ast_clear_flag ( conf , AST_FLAGS_ALL ) ;
conf - > max_size = DEFAULT_SIZE ;
conf - > resync_threshold = DEFAULT_RESYNC ;
ast_copy_string ( conf - > impl , " fixed " , sizeof ( conf - > impl ) ) ;
@ -886,6 +912,44 @@ static void hook_destroy_cb(void *framedata)
jb_framedata_destroy ( ( struct jb_framedata * ) framedata ) ;
}
static struct timeval jitterbuffer_frame_get_ntp_timestamp ( const struct jb_stream_sync * stream_sync , const struct ast_frame * frame )
{
int timestamp_diff ;
unsigned int rate ;
/* It's possible for us to receive frames before we receive the information allowing
* us to do NTP / RTP timestamp calculations . Since the information isn ' t available we
* can ' t generate one and give an empty timestamp .
*/
if ( ast_tvzero ( stream_sync - > ntp ) ) {
return ast_tv ( 0 , 0 ) ;
}
/* Convert the Asterisk timestamp into an RTP timestamp, and then based on the difference we can
* determine how many samples are in the frame and how long has elapsed since the synchronization
* RTP and NTP timestamps were received giving us the NTP timestamp for this frame .
*/
if ( frame - > frametype = = AST_FRAME_VOICE ) {
rate = ast_rtp_get_rate ( frame - > subclass . format ) ;
timestamp_diff = ( frame - > ts * ( rate / 1000 ) ) - stream_sync - > timestamp ;
} else {
/* Video is special - internally we reference it as 1000 to preserve the RTP timestamp but
* it is actualy 90000 , this is why we can just directly subtract the timestamp .
*/
rate = 90000 ;
timestamp_diff = frame - > ts - stream_sync - > timestamp ;
}
if ( timestamp_diff < 0 ) {
/* It's possible for us to be asked for an NTP timestamp from before our latest
* RTCP SR report . To handle this we subtract so we go back in time .
*/
return ast_tvsub ( stream_sync - > ntp , ast_samp2tv ( abs ( timestamp_diff ) , rate ) ) ;
} else {
return ast_tvadd ( stream_sync - > ntp , ast_samp2tv ( timestamp_diff , rate ) ) ;
}
}
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 ;
@ -928,6 +992,77 @@ static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_fram
return frame ;
}
if ( ast_test_flag ( & framedata - > jb_conf , AST_JB_SYNC_VIDEO ) ) {
if ( frame - > frametype = = AST_FRAME_VOICE ) {
/* Store the stream identifier for the audio stream so we can associate the incoming RTCP SR
* with the correct stream sync structure .
*/
framedata - > audio_stream_id = frame - > stream_num ;
} else if ( frame - > frametype = = AST_FRAME_RTCP & & frame - > subclass . integer = = AST_RTP_RTCP_SR ) {
struct ast_rtp_rtcp_report * rtcp_report = frame - > data . ptr ;
struct jb_stream_sync * stream_sync = NULL ;
/* Determine which stream this RTCP is in regards to */
if ( framedata - > audio_stream_id = = frame - > stream_num ) {
stream_sync = & framedata - > audio_stream_sync ;
} else if ( framedata - > video_stream_id = = frame - > stream_num ) {
stream_sync = & framedata - > video_stream_sync ;
}
if ( stream_sync ) {
/* Store the RTP and NTP timestamp mapping so we can derive an NTP timestamp for each frame */
stream_sync - > timestamp = rtcp_report - > sender_information . rtp_timestamp ;
stream_sync - > ntp = rtcp_report - > sender_information . ntp_timestamp ;
}
} else if ( frame - > frametype = = AST_FRAME_VIDEO ) {
/* If a video frame is late according to the audio timestamp don't stash it away, just return it.
* If however it is ahead then we keep it until such time as the audio catches up .
*/
struct ast_frame * jbframe ;
framedata - > video_stream_id = frame - > stream_num ;
/* If no timing information is available we can't store this away, so just let it through now */
if ( ! ast_test_flag ( frame , AST_FRFLAG_HAS_TIMING_INFO ) ) {
return frame ;
}
/* To ensure that the video starts when the audio starts we only start allowing frames through once
* audio starts flowing .
*/
if ( framedata - > audio_flowing ) {
struct timeval video_timestamp ;
video_timestamp = jitterbuffer_frame_get_ntp_timestamp ( & framedata - > video_stream_sync , frame ) ;
if ( ast_tvdiff_ms ( framedata - > last_audio_ntp_timestamp , video_timestamp ) > = 0 ) {
return frame ;
}
}
/* To prevent the early frame buffer from growing uncontrolled we impose a maximum count that it can
* get to . If this is reached then we drop a video frame , which should cause the receiver to ask for a
* new key frame .
*/
if ( framedata - > early_frame_count = = MAXIMUM_EARLY_FRAME_COUNT ) {
jbframe = AST_LIST_REMOVE_HEAD ( & framedata - > early_frames , frame_list ) ;
framedata - > early_frame_count - - ;
ast_frfree ( jbframe ) ;
}
jbframe = ast_frisolate ( frame ) ;
if ( ! jbframe ) {
/* If we can't isolate the frame the safest thing we can do is return it, even if the A/V sync
* may be off .
*/
return frame ;
}
AST_LIST_INSERT_TAIL ( & framedata - > early_frames , jbframe , frame_list ) ;
framedata - > early_frame_count + + ;
return & ast_null_frame ;
}
}
now_tv = ast_tvnow ( ) ;
now = ast_tvdiff_ms ( now_tv , framedata - > start_tv ) ;
@ -1022,6 +1157,8 @@ static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_fram
}
if ( frame - > frametype = = AST_FRAME_CONTROL ) {
struct ast_frame * early_frame ;
switch ( frame - > subclass . integer ) {
case AST_CONTROL_HOLD :
case AST_CONTROL_UNHOLD :
@ -1029,12 +1166,50 @@ static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_fram
case AST_CONTROL_SRCUPDATE :
case AST_CONTROL_SRCCHANGE :
framedata - > jb_impl - > force_resync ( framedata - > jb_obj ) ;
/* Since we are resyncing go ahead and clear out the video frames too */
while ( ( early_frame = AST_LIST_REMOVE_HEAD ( & framedata - > early_frames , frame_list ) ) ) {
ast_frfree ( early_frame ) ;
}
framedata - > audio_flowing = 0 ;
framedata - > early_frame_count = 0 ;
break ;
default :
break ;
}
}
/* If a voice frame is being passed through see if we need to add any additional frames to it */
if ( ast_test_flag ( & framedata - > jb_conf , AST_JB_SYNC_VIDEO ) & & frame - > frametype = = AST_FRAME_VOICE ) {
AST_LIST_HEAD_NOLOCK ( , ast_frame ) additional_frames ;
struct ast_frame * early_frame ;
/* We store the last NTP timestamp for the audio given to the core so that subsequents frames which
* are late can be passed immediately through ( this will occur for video frames which are returned here )
*/
framedata - > last_audio_ntp_timestamp = jitterbuffer_frame_get_ntp_timestamp ( & framedata - > audio_stream_sync , frame ) ;
framedata - > audio_flowing = 1 ;
AST_LIST_HEAD_INIT_NOLOCK ( & additional_frames ) ;
AST_LIST_TRAVERSE_SAFE_BEGIN ( & framedata - > early_frames , early_frame , frame_list ) {
struct timeval early_timestamp = jitterbuffer_frame_get_ntp_timestamp ( & framedata - > video_stream_sync , early_frame ) ;
int diff = ast_tvdiff_ms ( framedata - > last_audio_ntp_timestamp , early_timestamp ) ;
/* If this frame is from the past we need to include it with the audio frame that is going
* out .
*/
if ( diff > = 0 ) {
AST_LIST_REMOVE_CURRENT ( frame_list ) ;
framedata - > early_frame_count - - ;
AST_LIST_INSERT_TAIL ( & additional_frames , early_frame , frame_list ) ;
}
}
AST_LIST_TRAVERSE_SAFE_END ;
/* Append any additional frames we may want to include (such as video) */
AST_LIST_NEXT ( frame , frame_list ) = AST_LIST_FIRST ( & additional_frames ) ;
}
return frame ;
}
@ -1066,6 +1241,9 @@ static int jb_framedata_init(struct jb_framedata *framedata, struct ast_jb_conf
return - 1 ;
}
framedata - > audio_stream_id = - 1 ;
framedata - > video_stream_id = - 1 ;
AST_LIST_HEAD_INIT_NOLOCK ( & framedata - > early_frames ) ;
framedata - > timer_fd = ast_timer_fd ( framedata - > timer ) ;
framedata - > timer_interval = DEFAULT_TIMER_INTERVAL ;
ast_timer_set_rate ( framedata - > timer , 1000 / framedata - > timer_interval ) ;