int ast_slinfactory_feed(struct ast_slinfactory *sf, struct ast_frame *f) { struct ast_frame *begin_frame = f, *duped_frame = NULL, *frame_ptr; unsigned int x; /* In some cases, we can be passed a frame which has no data in it, but * which has a positive number of samples defined. Once such situation is * when a jitter buffer is in use and the jitter buffer interpolates a frame. * The frame it produces has data set to NULL, datalen set to 0, and samples * set to either 160 or 240. */ if (!f->data) { return 0; } if (f->subclass != AST_FORMAT_SLINEAR) { if (sf->trans && f->subclass != sf->format) { ast_translator_free_path(sf->trans); sf->trans = NULL; } if (!sf->trans) { if ((sf->trans = ast_translator_build_path(AST_FORMAT_SLINEAR, f->subclass)) == NULL) { ast_log(LOG_WARNING, "Cannot build a path from %s to slin\n", ast_getformatname(f->subclass)); return 0; } else { sf->format = f->subclass; } } if (!(begin_frame = ast_translate(sf->trans, f, 0))) return 0; duped_frame = ast_frdup(begin_frame); ast_frfree(begin_frame); if (!duped_frame) return 0; } else { if (!(duped_frame = ast_frdup(f))) return 0; } x = 0; AST_LIST_TRAVERSE(&sf->queue, frame_ptr, frame_list) x++; AST_LIST_INSERT_TAIL(&sf->queue, duped_frame, frame_list); sf->size += duped_frame->samples; return x; }
int ast_slinfactory_feed(struct ast_slinfactory *sf, struct ast_frame *f) { struct ast_frame *frame, *frame_ptr; if (!f) { return 0; } if (f->subclass != AST_FORMAT_SLINEAR) { if (sf->trans && f->subclass != sf->format) { ast_translator_free_path(sf->trans); sf->trans = NULL; } if (!sf->trans) { if ((sf->trans = ast_translator_build_path(AST_FORMAT_SLINEAR, f->subclass)) == NULL) { ast_log(LOG_WARNING, "Cannot build a path from %s to slin\n", ast_getformatname(f->subclass)); return 0; } else { sf->format = f->subclass; } } } if (sf->trans) { frame = ast_translate(sf->trans, f, 0); } else { frame = ast_frdup(f); } if (frame) { int x = 0; for (frame_ptr = sf->queue; frame_ptr && frame_ptr->next; frame_ptr=frame_ptr->next) { x++; } if (frame_ptr) { frame_ptr->next = frame; } else { sf->queue = frame; } frame->next = NULL; sf->size += frame->datalen; return x; } return 0; }
/*! \brief Allocator for T.38 data */ static struct t38_parameters_task_data *t38_parameters_task_data_alloc(struct ast_sip_session *session, struct ast_frame *frame) { struct t38_parameters_task_data *data = ao2_alloc(sizeof(*data), t38_parameters_task_data_destroy); if (!data) { return NULL; } data->session = session; ao2_ref(session, +1); data->frame = ast_frdup(frame); if (!data->frame) { ao2_ref(data, -1); data = NULL; } return data; }
conf_frame* create_conf_frame( struct ast_conf_member* member, conf_frame* next, const struct ast_frame* fr ) { // pointer to list of mixed frames conf_frame* cf = malloc( sizeof( struct conf_frame ) ) ; if ( cf == NULL ) { ast_log( LOG_ERROR, "unable to allocate memory for conf frame\n" ) ; return NULL ; } // // init with some defaults // // make sure converted frames are set to null // for ( int c = 0 ; c < AC_SUPPORTED_FORMATS ; ++c ) // { // cf->converted[ c ] = NULL ; // } memset( (struct ast_frame*)( cf->converted ), 0x0, ( sizeof( struct ast_frame* ) * AC_SUPPORTED_FORMATS ) ) ; cf->member = member ; // cf->priority = 0 ; cf->prev = NULL ; cf->next = next ; cf->static_frame = 0 ; // establish relationship to 'next' if ( next != NULL ) next->prev = cf ; // this holds the ast_frame pointer cf->fr = ( fr == NULL ) ? NULL : ast_frdup( ( struct ast_frame* )( fr ) ) ; // this holds the temporu mix buffer cf->mixed_buffer = NULL ; return cf ; }
static int transmit_audio(fax_session *s) { int res = -1; int original_read_fmt = AST_FORMAT_SLINEAR; int original_write_fmt = AST_FORMAT_SLINEAR; fax_state_t fax; struct ast_dsp *dsp = NULL; int detect_tone = 0; struct ast_frame *inf = NULL; struct ast_frame *fr; int last_state = 0; struct timeval now, start, state_change; enum ast_control_t38 t38control; original_read_fmt = s->chan->readformat; if (original_read_fmt != AST_FORMAT_SLINEAR) { res = ast_set_read_format(s->chan, AST_FORMAT_SLINEAR); if (res < 0) { ast_log(LOG_WARNING, "Unable to set to linear read mode, giving up\n"); goto done; } } original_write_fmt = s->chan->writeformat; if (original_write_fmt != AST_FORMAT_SLINEAR) { res = ast_set_write_format(s->chan, AST_FORMAT_SLINEAR); if (res < 0) { ast_log(LOG_WARNING, "Unable to set to linear write mode, giving up\n"); goto done; } } /* Initialize T30 terminal */ fax_init(&fax, s->caller_mode); /* Setup logging */ set_logging(&fax.logging); set_logging(&fax.t30_state.logging); /* Configure terminal */ set_local_info(&fax.t30_state, s); set_file(&fax.t30_state, s); set_ecm(&fax.t30_state, TRUE); fax_set_transmit_on_idle(&fax, TRUE); t30_set_phase_e_handler(&fax.t30_state, phase_e_handler, s); if (s->t38state == T38_STATE_UNAVAILABLE) { ast_debug(1, "T38 is unavailable on %s\n", s->chan->name); } else if (!s->direction) { /* We are receiving side and this means we are the side which should request T38 when the fax is detected. Use DSP to detect fax tone */ ast_debug(1, "Setting up CNG detection on %s\n", s->chan->name); dsp = ast_dsp_new(); ast_dsp_set_features(dsp, DSP_FEATURE_FAX_DETECT); ast_dsp_set_faxmode(dsp, DSP_FAXMODE_DETECT_CNG); detect_tone = 1; } start = state_change = ast_tvnow(); ast_activate_generator(s->chan, &generator, &fax); while (!s->finished) { res = ast_waitfor(s->chan, 20); if (res < 0) break; else if (res > 0) res = 0; inf = ast_read(s->chan); if (inf == NULL) { ast_debug(1, "Channel hangup\n"); res = -1; break; } ast_debug(10, "frame %d/%d, len=%d\n", inf->frametype, inf->subclass, inf->datalen); /* Detect fax tone */ if (detect_tone && inf->frametype == AST_FRAME_VOICE) { /* Duplicate frame because ast_dsp_process may free the frame passed */ fr = ast_frdup(inf); /* Do not pass channel to ast_dsp_process otherwise it may queue modified audio frame back */ fr = ast_dsp_process(NULL, dsp, fr); if (fr && fr->frametype == AST_FRAME_DTMF && fr->subclass == 'f') { ast_debug(1, "Fax tone detected. Requesting T38\n"); t38control = AST_T38_REQUEST_NEGOTIATE; ast_indicate_data(s->chan, AST_CONTROL_T38, &t38control, sizeof(t38control)); detect_tone = 0; } ast_frfree(fr); } /* Check the frame type. Format also must be checked because there is a chance that a frame in old format was already queued before we set chanel format to slinear so it will still be received by ast_read */ if (inf->frametype == AST_FRAME_VOICE && inf->subclass == AST_FORMAT_SLINEAR) { if (fax_rx(&fax, inf->data, inf->samples) < 0) { /* I know fax_rx never returns errors. The check here is for good style only */ ast_log(LOG_WARNING, "fax_rx returned error\n"); res = -1; break; } /* Watchdog */ if (last_state != fax.t30_state.state) { state_change = ast_tvnow(); last_state = fax.t30_state.state; } } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38 && inf->datalen == sizeof(enum ast_control_t38)) { t38control =*((enum ast_control_t38 *) inf->data); if (t38control == AST_T38_NEGOTIATED) { /* T38 switchover completed */ ast_debug(1, "T38 negotiated, finishing audio loop\n"); res = 1; break; } } ast_frfree(inf); inf = NULL; /* Watchdog */ now = ast_tvnow(); if (ast_tvdiff_sec(now, start) > WATCHDOG_TOTAL_TIMEOUT || ast_tvdiff_sec(now, state_change) > WATCHDOG_STATE_TIMEOUT) { ast_log(LOG_WARNING, "It looks like we hung. Aborting.\n"); res = -1; break; } } ast_debug(1, "Loop finished, res=%d\n", res); if (inf) ast_frfree(inf); if (dsp) ast_dsp_free(dsp); ast_deactivate_generator(s->chan); /* If we are switching to T38, remove phase E handler. Otherwise it will be executed by t30_terminate, display diagnostics and set status variables although no transmittion has taken place yet. */ if (res > 0) { t30_set_phase_e_handler(&fax.t30_state, NULL, NULL); } t30_terminate(&fax.t30_state); fax_release(&fax); done: if (original_write_fmt != AST_FORMAT_SLINEAR) { if (ast_set_write_format(s->chan, original_write_fmt) < 0) ast_log(LOG_WARNING, "Unable to restore write format on '%s'\n", s->chan->name); } if (original_read_fmt != AST_FORMAT_SLINEAR) { if (ast_set_read_format(s->chan, original_read_fmt) < 0) ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", s->chan->name); } return res; }
static int nv_detectfax_exec(struct ast_channel *chan, void *data) { int res = 0; struct ast_module_user *u; char tmp[256] = "\0"; char *p = NULL; char *waitstr = NULL; char *options = NULL; char *silstr = NULL; char *minstr = NULL; char *maxstr = NULL; struct ast_frame *fr = NULL; struct ast_frame *fr2 = NULL; int notsilent = 0; struct timeval start = {0, 0}, end = {0, 0}; int waitdur = 4; int sildur = 1000; int mindur = 100; int maxdur = -1; int skipanswer = 0; int noextneeded = 0; int ignoredtmf = 0; int ignorefax = 0; int ignoretalk = 0; int x = 0; int origrformat = 0; int features = 0; time_t timeout = 0; struct ast_dsp *dsp = NULL; pbx_builtin_setvar_helper(chan, "FAX_DETECTED", ""); pbx_builtin_setvar_helper(chan, "FAXEXTEN", ""); pbx_builtin_setvar_helper(chan, "DTMF_DETECTED", ""); pbx_builtin_setvar_helper(chan, "TALK_DETECTED", ""); if (data || !ast_strlen_zero((char *)data)) { strncpy(tmp, (char *)data, sizeof(tmp)-1); } p = tmp; waitstr = strsep(&p, "|"); options = strsep(&p, "|"); silstr = strsep(&p, "|"); minstr = strsep(&p, "|"); maxstr = strsep(&p, "|"); if (waitstr) { if ((sscanf(waitstr, "%d", &x) == 1) && (x > 0)) waitdur = x; } if (options) { if (strchr(options, 'n')) skipanswer = 1; if (strchr(options, 'x')) noextneeded = 1; if (strchr(options, 'd')) ignoredtmf = 1; if (strchr(options, 'f')) ignorefax = 1; if (strchr(options, 't')) ignoretalk = 1; } if (silstr) { if ((sscanf(silstr, "%d", &x) == 1) && (x > 0)) sildur = x; } if (minstr) { if ((sscanf(minstr, "%d", &x) == 1) && (x > 0)) mindur = x; } if (maxstr) { if ((sscanf(maxstr, "%d", &x) == 1) && (x > 0)) maxdur = x; } ast_log(LOG_DEBUG, "Preparing detect of fax (waitdur=%ds, sildur=%dms, mindur=%dms, maxdur=%dms)\n", waitdur, sildur, mindur, maxdur); u = ast_module_user_add(chan); if (chan->_state != AST_STATE_UP && !skipanswer) { /* Otherwise answer unless we're supposed to send this while on-hook */ res = ast_answer(chan); } if (!res) { origrformat = chan->readformat; if ((res = ast_set_read_format(chan, AST_FORMAT_SLINEAR))) ast_log(LOG_WARNING, "Unable to set read format to linear!\n"); } if (!(dsp = ast_dsp_new())) { ast_log(LOG_WARNING, "Unable to allocate DSP!\n"); res = -1; } if (dsp) { #if 0 if (!ignoretalk) ; /* features |= DSP_FEATURE_SILENCE_SUPPRESS; */ #endif if (!ignorefax) features |= DSP_FEATURE_FAX_DETECT; if (!ignoredtmf) features |= DSP_FEATURE_DTMF_DETECT; ast_dsp_set_threshold(dsp, 256); ast_dsp_set_features(dsp, features | DSP_DIGITMODE_RELAXDTMF); ast_dsp_digitmode(dsp, DSP_DIGITMODE_DTMF); } if (!res) { if (waitdur > 0) timeout = time(NULL) + (time_t)waitdur; while (ast_waitfor(chan, 50) > -1) { if (waitdur > 0 && time(NULL) > timeout) { res = 0; break; } fr = ast_read(chan); if (!fr) { ast_log(LOG_DEBUG, "Got hangup\n"); res = -1; break; } /* Duplicate frame because ast_dsp_process may free the frame passed */ fr2 = ast_frdup(fr); /* Do not pass channel to ast_dsp_process otherwise it may queue modified audio frame back */ fr2 = ast_dsp_process(NULL, dsp, fr2); if (!fr2) { ast_log(LOG_ERROR, "Bad DSP received (what happened?)\n"); ast_frfree(fr); res = 0; break; } if (fr2->frametype == AST_FRAME_DTMF) { if (fr2->subclass == 'f' && !ignorefax) { /* Fax tone -- Handle and return NULL */ ast_log(LOG_DEBUG, "Fax detected on %s\n", chan->name); if (strcmp(chan->exten, "fax")) { ast_log(LOG_NOTICE, "Redirecting '%s' to fax extension on context '%s'\n", chan->name, chan->context); pbx_builtin_setvar_helper(chan, "FAX_DETECTED", "1"); pbx_builtin_setvar_helper(chan, "FAXEXTEN", chan->exten); if (ast_exists_extension(chan, chan->context, "fax", 1, chan->CALLERID_FIELD)) { /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */ strncpy(chan->exten, "fax", sizeof(chan->exten)-1); chan->priority = 0; } else ast_log(LOG_WARNING, "Fax detected, but no fax extension\n"); } else ast_log(LOG_WARNING, "Already in a fax extension, not redirecting\n"); res = 0; ast_frfree(fr); ast_frfree(fr2); break; } else if (!ignoredtmf) { ast_log(LOG_DEBUG, "DTMF detected on %s\n", chan->name); char t[2]; t[0] = fr2->subclass; t[1] = '\0'; if (noextneeded || ast_canmatch_extension(chan, chan->context, t, 1, chan->CALLERID_FIELD)) { pbx_builtin_setvar_helper(chan, "DTMF_DETECTED", "1"); /* They entered a valid extension, or might be anyhow */ if (noextneeded) { ast_log(LOG_NOTICE, "DTMF received (not matching to exten)\n"); res = 0; } else { ast_log(LOG_NOTICE, "DTMF received (matching to exten)\n"); res = fr2->subclass; } ast_frfree(fr); ast_frfree(fr2); break; } else ast_log(LOG_DEBUG, "Valid extension requested and DTMF did not match\n"); } } else if ((fr->frametype == AST_FRAME_VOICE) && (fr->subclass == AST_FORMAT_SLINEAR) && !ignoretalk) { int totalsilence; int ms; res = ast_dsp_silence(dsp, fr, &totalsilence); if (res && (totalsilence > sildur)) { /* We've been quiet a little while */ if (notsilent) { /* We had heard some talking */ gettimeofday(&end, NULL); ms = (end.tv_sec - start.tv_sec) * 1000; ms += (end.tv_usec - start.tv_usec) / 1000; ms -= sildur; if (ms < 0) ms = 0; if ((ms > mindur) && ((maxdur < 0) || (ms < maxdur))) { char ms_str[10]; ast_log(LOG_DEBUG, "Found qualified token of %d ms\n", ms); ast_log(LOG_NOTICE, "Redirecting %s to talk extension\n", chan->name); /* Save detected talk time (in milliseconds) */ sprintf(ms_str, "%d", ms); pbx_builtin_setvar_helper(chan, "TALK_DETECTED", ms_str); if (ast_exists_extension(chan, chan->context, "talk", 1, chan->CALLERID_FIELD)) { strncpy(chan->exten, "talk", sizeof(chan->exten) - 1); chan->priority = 0; } else ast_log(LOG_WARNING, "Talk detected, but no talk extension\n"); res = 0; ast_frfree(fr); ast_frfree(fr2); break; } else ast_log(LOG_DEBUG, "Found unqualified token of %d ms\n", ms); notsilent = 0; } } else { if (!notsilent) { /* Heard some audio, mark the begining of the token */ gettimeofday(&start, NULL); ast_log(LOG_DEBUG, "Start of voice token!\n"); notsilent = 1; } } } ast_frfree(fr); ast_frfree(fr2); } } else ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name); if (res > -1) { if (origrformat && ast_set_read_format(chan, origrformat)) { ast_log(LOG_WARNING, "Failed to restore read format for %s to %s\n", chan->name, ast_getformatname(origrformat)); } } if (dsp) ast_dsp_free(dsp); ast_module_user_remove(u); return res; }
int ast_jb_put(struct ast_channel *chan, struct ast_frame *f) { struct ast_jb *jb = ast_channel_jb(chan); const struct ast_jb_impl *jbimpl = jb->impl; void *jbobj = jb->jbobj; struct ast_frame *frr; long now = 0; if (!ast_test_flag(jb, JB_USE)) return -1; if (f->frametype != AST_FRAME_VOICE) { if (f->frametype == AST_FRAME_DTMF && ast_test_flag(jb, JB_CREATED)) { jb_framelog("JB_PUT {now=%ld}: Received DTMF frame. Force resynching jb...\n", now); jbimpl->force_resync(jbobj); } return -1; } /* We consider an enabled jitterbuffer should receive frames with valid timing info. */ if (!ast_test_flag(f, AST_FRFLAG_HAS_TIMING_INFO) || f->len < 2 || f->ts < 0) { ast_log(LOG_WARNING, "%s received frame with invalid timing info: " "has_timing_info=%d, len=%ld, ts=%ld, src=%s\n", ast_channel_name(chan), ast_test_flag(f, AST_FRFLAG_HAS_TIMING_INFO), f->len, f->ts, f->src); return -1; } frr = ast_frdup(f); if (!frr) { ast_log(LOG_ERROR, "Failed to isolate frame for the jitterbuffer on channel '%s'\n", ast_channel_name(chan)); return -1; } if (!ast_test_flag(jb, JB_CREATED)) { if (create_jb(chan, frr)) { ast_frfree(frr); /* Disable the jitterbuffer */ ast_clear_flag(jb, JB_USE); return -1; } ast_set_flag(jb, JB_CREATED); return 0; } else { now = get_now(jb, NULL); if (jbimpl->put(jbobj, frr, now) != AST_JB_IMPL_OK) { jb_framelog("JB_PUT {now=%ld}: Dropped frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); ast_frfree(frr); /*return -1;*/ /* TODO: Check this fix - should return 0 here, because the dropped frame shouldn't be delivered at all */ return 0; } jb->next = jbimpl->next(jbobj); jb_framelog("JB_PUT {now=%ld}: Queued frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); return 0; } }
conf_frame* mix_single_speaker( conf_frame* frames_in, int volume ) { #ifdef APP_KONFERENCE_DEBUG //DEBUG("returning single spoken frame\n") ; // // check input // if ( frames_in == NULL ) { DEBUG("unable to mix single spoken frame with null frame\n") ; return NULL ; } if ( frames_in->fr == NULL ) { DEBUG("unable to mix single spoken frame with null data\n") ; return NULL ; } if ( frames_in->member == NULL ) { DEBUG("unable to mix single spoken frame with null member\n") ; return NULL ; } #endif // APP_KONFERENCE_DEBUG // // 'mix' the frame // // copy orignal frame to converted array so listeners don't need to re-encode it frames_in->converted[ frames_in->member->read_format_index ] = ast_frdup( frames_in->fr ) ; // convert frame to slinear, if we have a path if (!(frames_in->fr = convert_frame_to_slinear( frames_in->member->to_slinear, frames_in->fr))) { ast_log( LOG_WARNING, "mix_single_speaker: unable to convert frame to slinear\n" ) ; return frames_in ; } if ( (frames_in->member->talk_volume != 0) || (volume != 0) ) { ast_frame_adjust_volume(frames_in->fr, frames_in->member->talk_volume + volume); } if (!frames_in->member->spy_partner) { // speaker is neither a spyee nor a spyer // set the frame's member to null ( i.e. all listeners ) frames_in->member = NULL ; } else { // speaker is either a spyee or a spyer if ( frames_in->member->spyee_channel_name == NULL ) { conf_frame *spy_frame = copy_conf_frame(frames_in); if ( spy_frame != 0 ) { frames_in->next = spy_frame; spy_frame->prev = frames_in; spy_frame->member = frames_in->member->spy_partner; } frames_in->member = NULL ; } else frames_in->member = frames_in->member->spy_partner ; } return frames_in ; }
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; int putframe = 0; /* signifies if audio frame was placed into the buffer or not */ 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; } if (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) { if (ast_timer_ack(framedata->timer, 1) < 0) { ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n"); return frame; } } 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); ao2_replace(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) { if (jbframe != frame) { ast_frfree(frame); } frame = &ast_null_frame; } else if (jbframe != frame) { ast_frfree(jbframe); } putframe = 1; } if (frame->frametype == AST_FRAME_NULL) { int res; long next = framedata->jb_impl->next(framedata->jb_obj); /* 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. */ if (now < next) { long int diff = next - now; if (!putframe) { return frame; } else if (diff >= framedata->timer_interval) { return frame; } } ast_frfree(frame); frame = &ast_null_frame; 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) { struct ast_frame tmp = { 0, }; tmp.frametype = AST_FRAME_VOICE; tmp.subclass.format = framedata->last_format; /* example: 8000hz / (1000 / 20ms) = 160 samples */ tmp.samples = ast_format_get_sample_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"; ast_frfree(frame); frame = ast_frdup(&tmp); break; } /* else fall through */ case AST_JB_IMPL_NOFRAME: ast_frfree(frame); frame = &ast_null_frame; break; } } if (frame->frametype == AST_FRAME_CONTROL) { switch(frame->subclass.integer) { case AST_CONTROL_HOLD: case AST_CONTROL_UNHOLD: case AST_CONTROL_T38_PARAMETERS: case AST_CONTROL_SRCUPDATE: case AST_CONTROL_SRCCHANGE: framedata->jb_impl->force_resync(framedata->jb_obj); break; default: break; } } return frame; }