pj_status_t latency_checker::get_frame(pjmedia_frame *frame) { PPJ_WaitAndLock lock(*m_lock); if(frame&&(frame->buf)) { bool gen_active = false; pj_timestamp now; pj_get_timestamp(&now); switch(m_dstate) { case 1: { if((pj_elapsed_msec(&m_start_tone_time,&now)>100)&&(pj_elapsed_msec(&m_last_get_frame_time,&now)>m_config.min_frame_length/2)) { PJ_LOG_(DEBUG_LEVEL,("LATENCY","Starting ACTIVE tone.")); m_start_tone_time = now; m_dstate = 2; m_dcnt = 0; gen_active = true; } break; } case 2: { gen_active = m_dcnt++<3; if(pj_elapsed_msec(&m_start_tone_time,&now)>2000) { m_dstate = 0; PJ_LOG_(DEBUG_LEVEL,("LATENCY","ACTIVE not found. Reseting.")); } break; } }; if(gen_active) { generate_dual_tone(&m_active_tone,1,frame->size/2,(short*)(frame->buf)); } else { generate_dual_tone(&m_idle_tone,1,frame->size/2,(short*)(frame->buf)); } } pj_get_timestamp(&m_last_get_frame_time); return PJ_SUCCESS; }
static pj_status_t play_cb(void *user_data, pj_uint32_t timestamp, void *output, unsigned size) { static pj_timestamp last_cb; PJ_UNUSED_ARG(user_data); PJ_UNUSED_ARG(output); PJ_UNUSED_ARG(size); ++play_counter; last_play_timestamp = timestamp; if (last_cb.u64 == 0) { pj_get_timestamp(&last_cb); } else if (play_counter <= PJ_ARRAY_SIZE(play_delays)) { pj_timestamp now; unsigned delay; pj_get_timestamp(&now); delay = pj_elapsed_msec(&last_cb, &now); if (delay < min_delay) min_delay = delay; if (delay > max_delay) max_delay = delay; last_cb = now; play_delays[play_counter-1] = (char)delay; } return PJ_SUCCESS; }
/* * Clock thread */ static int clock_thread(void *arg) { pj_timestamp now; pjmedia_clock *clock = (pjmedia_clock*) arg; /* Set thread priority to maximum unless not wanted. */ if ((clock->options & PJMEDIA_CLOCK_NO_HIGHEST_PRIO) == 0) { int max = pj_thread_get_prio_max(pj_thread_this()); if (max > 0) pj_thread_set_prio(pj_thread_this(), max); } //printf("%s:------------11--------------\n", THIS_FILE); /* Get the first tick */ pj_get_timestamp(&clock->next_tick); clock->next_tick.u64 += clock->interval.u64; while (!clock->quitting) { pj_get_timestamp(&now); /* Wait for the next tick to happen */ if (now.u64 < clock->next_tick.u64) { unsigned msec; msec = pj_elapsed_msec(&now, &clock->next_tick); pj_thread_sleep(msec); } //printf("%s:------------12--------------, 0x%02x, %d\n", THIS_FILE, clock, (int)(clock->quitting)); /* Skip if not running */ if (!clock->running) { /* Calculate next tick */ clock_calc_next_tick(clock, &now); continue; } pj_lock_acquire(clock->lock); //printf("%s:------------13--------------, 0x%02x, %d\n", THIS_FILE, clock, (int)(clock->quitting)); /* Call callback, if any */ if (clock->cb) (*clock->cb)(&clock->timestamp, clock->user_data); /* Best effort way to detect if we've been destroyed in the callback */ if (clock->quitting) break; /* Increment timestamp */ clock->timestamp.u64 += clock->timestamp_inc; //printf("%s:------------14--------------, 0x%02x, %d\n", THIS_FILE, clock, (int)(clock->quitting)); /* Calculate next tick */ clock_calc_next_tick(clock, &now); //printf("%s:------------15--------------\n", THIS_FILE); pj_lock_release(clock->lock); } return 0; }
pj_status_t latency_checker::put_frame(const pjmedia_frame *frame) { m_gain->process((short*)frame->buf,frame->size/2,0,NULL); PPJ_WaitAndLock lock(*m_lock); pj_uint16_t detected = 0; pj_int32_t if1,if2,af1,af2; if1 = m_idle_freq1_det->detect((short*)frame->buf,frame->size/2); if2 = m_idle_freq2_det->detect((short*)frame->buf,frame->size/2); af1 = m_active_freq1_det->detect((short*)frame->buf,frame->size/2); af2 = m_active_freq2_det->detect((short*)frame->buf,frame->size/2); if((if1>MIN_LEVEL1)&&(if2>MIN_LEVEL2)&&ratio(8,if1,af1)&&ratio(5,if1,af2)&&ratio(3,if2,af1)&&ratio(10,if2,af2)) detected = 1; else if((af1>MIN_LEVEL1)&&(af2>MIN_LEVEL2)&&ratio(10,af1,if1)&&ratio(10,af1,if2)&&ratio(3,af2,if1)&&ratio(10,af2,if2)) detected = 2; m_quality = m_quality*0.95; if(detected) m_quality += 5; pj_timestamp now; pj_get_timestamp(&now); switch(m_dstate) { case 0: { if(detected==1) { PJ_LOG_(DEBUG_LEVEL,("LATENCY","Detected IDLE on wait.")); m_start_tone_time = now; m_dstate = 1; } break; } case 2: { if(detected==2) { m_latency = pj_elapsed_msec(&m_start_tone_time,&now); PJ_LOG_(DEBUG_LEVEL,("LATENCY","Detected ACTIVE.Latency = %d", m_latency)); m_dstate = 0; } break; } }; sprintf(m_status_string,"TONE: %d (%d, %d, %d, %d)",detected,if1,if2,af1,af2); //PJ_LOG_(DEBUG_LEVEL,("LATENCY","rec %d (%d, %d, %d, %d)",detected,if1,if2,af1,af2)); return PJ_SUCCESS; }
pjmedia_clock_src_get_current_timestamp( const pjmedia_clock_src *clocksrc, pj_timestamp *timestamp) { pj_timestamp now; unsigned elapsed_ms; PJ_ASSERT_RETURN(clocksrc && timestamp, PJ_EINVAL); pj_get_timestamp(&now); elapsed_ms = pj_elapsed_msec(&clocksrc->last_update, &now); pj_memcpy(timestamp, &clocksrc->timestamp, sizeof(pj_timestamp)); pj_add_timestamp32(timestamp, elapsed_ms * clocksrc->clock_rate / 1000); return PJ_SUCCESS; }
/* * Poll the clock. */ PJ_DEF(pj_bool_t) pjmedia_clock_wait( pjmedia_clock *clock, pj_bool_t wait, pj_timestamp *ts) { pj_timestamp now; pj_status_t status; PJ_ASSERT_RETURN(clock != NULL, PJ_FALSE); PJ_ASSERT_RETURN((clock->options & PJMEDIA_CLOCK_NO_ASYNC) != 0, PJ_FALSE); PJ_ASSERT_RETURN(clock->running, PJ_FALSE); status = pj_get_timestamp(&now); if (status != PJ_SUCCESS) return PJ_FALSE; /* Wait for the next tick to happen */ if (now.u64 < clock->next_tick.u64) { unsigned msec; if (!wait) return PJ_FALSE; msec = pj_elapsed_msec(&now, &clock->next_tick); pj_thread_sleep(msec); } /* Call callback, if any */ if (clock->cb) (*clock->cb)(&clock->timestamp, clock->user_data); /* Report timestamp to caller */ if (ts) ts->u64 = clock->timestamp.u64; /* Increment timestamp */ clock->timestamp.u64 += clock->samples_per_frame; /* Calculate next tick */ clock->next_tick.u64 += clock->interval.u64; /* Done */ return PJ_TRUE; }
/* * Clock thread */ static int clock_thread(void *arg) { pj_timestamp now; pjmedia_clock *clock = arg; /* Get the first tick */ pj_get_timestamp(&clock->next_tick); clock->next_tick.u64 += clock->interval.u64; while (!clock->quitting) { pj_get_timestamp(&now); /* Wait for the next tick to happen */ if (now.u64 < clock->next_tick.u64) { unsigned msec; msec = pj_elapsed_msec(&now, &clock->next_tick); pj_thread_sleep(msec); } /* Skip if not running */ if (!clock->running) continue; pj_lock_acquire(clock->lock); /* Call callback, if any */ if (clock->cb) (*clock->cb)(&clock->timestamp, clock->user_data); /* Increment timestamp */ clock->timestamp.u64 += clock->samples_per_frame; /* Calculate next tick */ clock->next_tick.u64 += clock->interval.u64; pj_lock_release(clock->lock); } return 0; }
/* * main() */ int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *wav_play; pjmedia_port *wav_rec; pjmedia_port *wav_out; pj_status_t status; pjmedia_echo_state *ec; pjmedia_frame play_frame, rec_frame; unsigned opt = 0; unsigned latency_ms = 25; unsigned tail_ms = TAIL_LENGTH; pj_timestamp t0, t1; int i, repeat=1, interactive=0, c; pj_optind = 0; while ((c=pj_getopt(argc, argv, "d:l:a:r:i")) !=-1) { switch (c) { case 'd': latency_ms = atoi(pj_optarg); if (latency_ms < 25) { puts("Invalid delay"); puts(desc); } break; case 'l': tail_ms = atoi(pj_optarg); break; case 'a': { int alg = atoi(pj_optarg); switch (alg) { case 0: opt = 0; case 1: opt = PJMEDIA_ECHO_SPEEX; break; case 3: opt = PJMEDIA_ECHO_SIMPLE; break; default: puts("Invalid algorithm"); puts(desc); return 1; } } break; case 'r': repeat = atoi(pj_optarg); if (repeat < 1) { puts("Invalid repeat count"); puts(desc); return 1; } break; case 'i': interactive = 1; break; } } if (argc - pj_optind != 3) { puts("Error: missing argument(s)"); puts(desc); return 1; } /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for our file player */ pool = pj_pool_create( &cp.factory, /* pool factory */ "wav", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Open wav_play */ status = pjmedia_wav_player_port_create(pool, argv[pj_optind], PTIME, PJMEDIA_FILE_NO_LOOP, 0, &wav_play); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening playback WAV file", status); return 1; } /* Open recorded wav */ status = pjmedia_wav_player_port_create(pool, argv[pj_optind+1], PTIME, PJMEDIA_FILE_NO_LOOP, 0, &wav_rec); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening recorded WAV file", status); return 1; } /* play and rec WAVs must have the same clock rate */ if (wav_play->info.clock_rate != wav_rec->info.clock_rate) { puts("Error: clock rate mismatch in the WAV files"); return 1; } /* .. and channel count */ if (wav_play->info.channel_count != wav_rec->info.channel_count) { puts("Error: clock rate mismatch in the WAV files"); return 1; } /* Create output wav */ status = pjmedia_wav_writer_port_create(pool, argv[pj_optind+2], wav_play->info.clock_rate, wav_play->info.channel_count, wav_play->info.samples_per_frame, wav_play->info.bits_per_sample, 0, 0, &wav_out); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening output WAV file", status); return 1; } /* Create echo canceller */ status = pjmedia_echo_create2(pool, wav_play->info.clock_rate, wav_play->info.channel_count, wav_play->info.samples_per_frame, tail_ms, latency_ms, opt, &ec); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error creating EC", status); return 1; } /* Processing loop */ play_frame.buf = pj_pool_alloc(pool, wav_play->info.samples_per_frame<<1); rec_frame.buf = pj_pool_alloc(pool, wav_play->info.samples_per_frame<<1); pj_get_timestamp(&t0); for (i=0; i < repeat; ++i) { for (;;) { play_frame.size = wav_play->info.samples_per_frame << 1; status = pjmedia_port_get_frame(wav_play, &play_frame); if (status != PJ_SUCCESS) break; status = pjmedia_echo_playback(ec, (short*)play_frame.buf); rec_frame.size = wav_play->info.samples_per_frame << 1; status = pjmedia_port_get_frame(wav_rec, &rec_frame); if (status != PJ_SUCCESS) break; status = pjmedia_echo_capture(ec, (short*)rec_frame.buf, 0); //status = pjmedia_echo_cancel(ec, (short*)rec_frame.buf, // (short*)play_frame.buf, 0, NULL); pjmedia_port_put_frame(wav_out, &rec_frame); } pjmedia_wav_player_port_set_pos(wav_play, 0); pjmedia_wav_player_port_set_pos(wav_rec, 0); } pj_get_timestamp(&t1); i = pjmedia_wav_writer_port_get_pos(wav_out) / sizeof(pj_int16_t) * 1000 / (wav_out->info.clock_rate * wav_out->info.channel_count); PJ_LOG(3,(THIS_FILE, "Processed %3d.%03ds audio", i / 1000, i % 1000)); PJ_LOG(3,(THIS_FILE, "Completed in %u msec\n", pj_elapsed_msec(&t0, &t1))); /* Destroy file port(s) */ status = pjmedia_port_destroy( wav_play ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_port_destroy( wav_rec ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_port_destroy( wav_out ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Destroy ec */ pjmedia_echo_destroy(ec); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); if (interactive) { char s[10], *dummy; puts("ENTER to quit"); dummy = fgets(s, sizeof(s), stdin); } /* Done. */ return 0; }
static int PJ_THREAD_FUNC AndroidRecorderCallback(void* userData){ struct android_aud_stream *stream = (struct android_aud_stream*) userData; JNIEnv *jni_env = 0; ATTACH_JVM(jni_env); jmethodID read_method=0, record_method=0; int bytesRead; int size = stream->samples_per_frame * stream->bytes_per_sample; int nframes = stream->samples_per_frame / stream->channel_count; jbyte* buf; pj_status_t status = 0; jbyteArray inputBuffer; pj_timestamp tstamp, now, last_frame; int elapsed_time = 0; //Frame time in ms int frame_time = nframes * 1000 / stream->samples_per_sec; int missed_time = frame_time; int to_wait = 0; PJ_LOG(3,(THIS_FILE, "<< Enter recorder thread")); if(!stream->record){ goto on_break; } //Get methods ids read_method = jni_env->GetMethodID(stream->record_class,"read", "([BII)I"); record_method = jni_env->GetMethodID(stream->record_class,"startRecording", "()V"); if(read_method==0 || record_method==0) { goto on_break; } //Create a buffer for frames read inputBuffer = jni_env->NewByteArray(size); if (inputBuffer == 0) { PJ_LOG(2, (THIS_FILE, "Not able to allocate a buffer for input read process")); goto on_break; } //start recording //setpriority(PRIO_PROCESS, 0, -19 /*ANDROID_PRIORITY_AUDIO*/); // set priority is probably not enough cause does not change the thread group in scheduler // Temporary solution is to call the java api to set the thread priority. // A cool solution would be to port (if possible) the code from the android os regarding set_sched groups set_android_thread_priority(THREAD_PRIORITY_URGENT_AUDIO); buf = jni_env->GetByteArrayElements(inputBuffer, 0); //Init everything tstamp.u64 = 0; pj_bzero (buf, size); jni_env->CallVoidMethod(stream->record, record_method); pj_get_timestamp(&last_frame); while ( !stream->quit_flag ) { pj_bzero (buf, size); #if COMPATIBLE_ALSA pj_get_timestamp(&now); // Time between now and last frame next frame (ms) elapsed_time = pj_elapsed_msec(&last_frame, &now); pj_get_timestamp(&last_frame); //PJ_LOG (4, (THIS_FILE, "Elapsed time is %d | missed time is %d | frame time %d", elapsed_time, missed_time, frame_time)); //Update missed time // Positif if we are late // negatif if we are earlier // dividing by 2 is empiric result // on N1 if not we get buffer overflow I assume that it fill packets faster than the frequency missed_time = missed_time/2 + elapsed_time - frame_time; //PJ_LOG (4, (THIS_FILE, "And now :: Elapsed time is %d | missed time is %d", elapsed_time, missed_time)); //If we go faster than the buffer filling we have to wait no if( missed_time <= 0 ){ //if(elapsed_time < frame_time){ to_wait = - missed_time - 2; if(to_wait > 0){ // PJ_LOG (4, (THIS_FILE, "Wait for %d / %d", to_wait, frame_time)); pj_thread_sleep(to_wait); } //} } /* //PJ_LOG (4, (THIS_FILE, "Next frame %d", next_frame_in)); if (next_frame_in-2 > 0) { //PJ_LOG (4, (THIS_FILE, "Wait for buffer %d", next_frame_in)); pj_thread_sleep(next_frame_in-5); //Reset the delay we have regarding next frame retard = 0; }else{ if(next_frame_in < 0){ retard += next_frame_in; } } */ #endif bytesRead = jni_env->CallIntMethod(stream->record, read_method, inputBuffer, 0, size); if(bytesRead<=0){ PJ_LOG (3, (THIS_FILE, "Record thread : error while reading data... is there something we can do here? %d", bytesRead)); continue; } if(stream->quit_flag){ break; } if(bytesRead != size){ PJ_LOG(3, (THIS_FILE, "Overrun...")); continue; } // PJ_LOG(4,(THIS_FILE, "Valid record frame read")); //jni_env->GetByteArrayRegion(inputBuffer, 0, size, buf ); pjmedia_frame frame; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.size = size; frame.bit_info = 0; frame.buf = (void*) buf; frame.timestamp.u64 = tstamp.u64; // PJ_LOG(3, (THIS_FILE, "New audio record frame to treat : %d <size : %d>", frame.type, frame.size)); status = (*stream->rec_cb)(stream->user_data, &frame); // PJ_LOG(4,(THIS_FILE, "Valid record frame sent to network stack")); if (status != PJ_SUCCESS){ PJ_LOG(1, (THIS_FILE, "Error in record callback")); goto on_finish; } //Update for next step tstamp.u64 += nframes; }; on_finish: jni_env->ReleaseByteArrayElements(inputBuffer, buf, 0); jni_env->DeleteLocalRef(inputBuffer); on_break: DETACH_JVM(jni_env); PJ_LOG(3,(THIS_FILE, ">> Record thread stopped")); // pj_sem_post(stream->audio_launch_sem); stream->rec_thread_exited = 1; return 0; }
pj_uint32_t to_msec() const { Pj_Timestamp zero; pj_memset(&zero, 0, sizeof(zero)); return pj_elapsed_msec(&zero.ts_, &ts_); }
static int sleep_duration_test(void) { enum { MIS = 20}; unsigned duration[] = { 2000, 1000, 500, 200, 100 }; unsigned i; pj_status_t rc; PJ_LOG(3,(THIS_FILE, "..running sleep duration test")); /* Test pj_thread_sleep() and pj_gettimeofday() */ for (i=0; i<PJ_ARRAY_SIZE(duration); ++i) { pj_time_val start, stop; pj_uint32_t msec; /* Mark start of test. */ rc = pj_gettimeofday(&start); if (rc != PJ_SUCCESS) { app_perror("...error: pj_gettimeofday()", rc); return -10; } /* Sleep */ rc = pj_thread_sleep(duration[i]); if (rc != PJ_SUCCESS) { app_perror("...error: pj_thread_sleep()", rc); return -20; } /* Mark end of test. */ rc = pj_gettimeofday(&stop); /* Calculate duration (store in stop). */ PJ_TIME_VAL_SUB(stop, start); /* Convert to msec. */ msec = PJ_TIME_VAL_MSEC(stop); /* Check if it's within range. */ if (msec < duration[i] * (100-MIS)/100 || msec > duration[i] * (100+MIS)/100) { PJ_LOG(3,(THIS_FILE, "...error: slept for %d ms instead of %d ms " "(outside %d%% err window)", msec, duration[i], MIS)); return -30; } } /* Test pj_thread_sleep() and pj_get_timestamp() and friends */ for (i=0; i<PJ_ARRAY_SIZE(duration); ++i) { pj_time_val t1, t2; pj_timestamp start, stop; pj_uint32_t msec; pj_thread_sleep(0); /* Mark start of test. */ rc = pj_get_timestamp(&start); if (rc != PJ_SUCCESS) { app_perror("...error: pj_get_timestamp()", rc); return -60; } /* ..also with gettimeofday() */ pj_gettimeofday(&t1); /* Sleep */ rc = pj_thread_sleep(duration[i]); if (rc != PJ_SUCCESS) { app_perror("...error: pj_thread_sleep()", rc); return -70; } /* Mark end of test. */ pj_get_timestamp(&stop); /* ..also with gettimeofday() */ pj_gettimeofday(&t2); /* Compare t1 and t2. */ if (PJ_TIME_VAL_LT(t2, t1)) { PJ_LOG(3,(THIS_FILE, "...error: t2 is less than t1!!")); return -75; } /* Get elapsed time in msec */ msec = pj_elapsed_msec(&start, &stop); /* Check if it's within range. */ if (msec < duration[i] * (100-MIS)/100 || msec > duration[i] * (100+MIS)/100) { PJ_LOG(3,(THIS_FILE, "...error: slept for %d ms instead of %d ms " "(outside %d%% err window)", msec, duration[i], MIS)); PJ_TIME_VAL_SUB(t2, t1); PJ_LOG(3,(THIS_FILE, "...info: gettimeofday() reported duration is " "%d msec", PJ_TIME_VAL_MSEC(t2))); return -76; } } /* All done. */ return 0; }
PJ_DEF(Frame_Buffer *) jitter_buffer_getCompleteFrameForDecoding(Jitter_Buffer *jitter_buffer, pj_uint32_t max_wait_time_ms) { //running if(!jitter_buffer->running) return NULL; if(pj_mutex_lock(jitter_buffer->jb_mutex) != PJ_SUCCESS) { //error return NULL; } /*{ char buf[1024]; getFrameInfo(&jitter_buffer->frameList, &jitter_buffer->decode_state, buf, 1024); PJ_LOG(4, (THIS_FILE, "jb status:\n%s\n", buf)); }*/ //the first frame must be a key frame if(jitter_buffer->decode_state.inited == PJ_FALSE) jitter_buffer->waiting_for_key_frame == PJ_TRUE; //clean old frames CleanOldFrames(jitter_buffer); //get complete , continuous frame Frame_Buffer * frame = findOldestCompleteContinuousFrame(jitter_buffer); //if not got, try to wait a frame if(frame == NULL) { if(max_wait_time_ms == 0) goto ON_RET; pj_timestamp current ; pj_get_timestamp(¤t); pj_timestamp end = current; pj_add_timestamp32(&end, max_wait_time_ms); pj_int32_t wait_time_ms = max_wait_time_ms; event_reset(jitter_buffer->packet_event); while(wait_time_ms > 0) { pj_mutex_unlock(jitter_buffer->jb_mutex); if(event_wait(jitter_buffer->packet_event, wait_time_ms) == WAIT_RET_SIGNAL) { //incoming a packet pj_mutex_lock(jitter_buffer->jb_mutex); if(!jitter_buffer->running) //jitter buffer stoped goto ON_RET; CleanOldFrames(jitter_buffer); frame = findOldestCompleteContinuousFrame(jitter_buffer); if(frame != NULL) break; pj_get_timestamp(¤t); int elapsed_msec = pj_elapsed_msec(¤t, &end); wait_time_ms = elapsed_msec; } else { pj_mutex_lock(jitter_buffer->jb_mutex); //error or timeout break; } } } else event_reset(jitter_buffer->packet_event); if(frame == NULL) goto ON_RET; /*if(jitter_buffer->waiting_for_key_frame && !frame->session_info.isKeyFrame) { frame = NULL; //not a key frame goto ON_RET; }*/ //got one, update jitter if(frame->nack_count > 0) { jitter_estimator_frameNacked(&jitter_buffer->jitter_estimator); //nacked } else { //update jitter estimator UpdateJitterEstimatorForFrame(jitter_buffer, frame); } //set frame status frame_buffer_setStatus(frame, FRAME_STATUS_DECODING); //update decode state decode_state_updateFrame(frame, &jitter_buffer->decode_state); //waiting for key frame if(frame->session_info.isKeyFrame) jitter_buffer->waiting_for_key_frame = PJ_FALSE; //clean old frames CleanOldFrames(jitter_buffer); //return frame ON_RET: pj_mutex_unlock(jitter_buffer->jb_mutex); return frame; }