2014-02-16 05:36:15 +08:00
|
|
|
/** media.c
|
2014-02-17 09:01:30 +08:00
|
|
|
*
|
2014-02-16 05:36:15 +08:00
|
|
|
* Audio and video codec intitialization, encoding/decoding and playback
|
2013-10-13 22:14:27 +08:00
|
|
|
*
|
2014-02-16 05:36:15 +08:00
|
|
|
* Copyright (C) 2013 Tox project All Rights Reserved.
|
2013-10-13 22:14:27 +08:00
|
|
|
*
|
2014-02-16 05:36:15 +08:00
|
|
|
* This file is part of Tox.
|
2013-10-13 22:14:27 +08:00
|
|
|
*
|
2014-02-16 05:36:15 +08:00
|
|
|
* Tox 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 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
2013-10-13 22:14:27 +08:00
|
|
|
*
|
2014-02-16 05:36:15 +08:00
|
|
|
* Tox 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.
|
2013-10-13 22:14:27 +08:00
|
|
|
*
|
2014-02-16 05:36:15 +08:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with Tox. If not, see <http://www.gnu.org/licenses/>.
|
2013-10-13 22:14:27 +08:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
#include "../toxcore/logger.h"
|
|
|
|
|
2013-10-13 22:14:27 +08:00
|
|
|
#include <stdio.h>
|
2014-02-16 03:44:33 +08:00
|
|
|
#include <stdlib.h>
|
2013-10-13 22:14:27 +08:00
|
|
|
#include <math.h>
|
2014-02-01 19:52:48 +08:00
|
|
|
#include <assert.h>
|
2013-10-13 22:14:27 +08:00
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
#include "rtp.h"
|
2014-06-21 07:58:55 +08:00
|
|
|
#include "codec.h"
|
|
|
|
|
2014-06-27 00:36:44 +08:00
|
|
|
const uint16_t min_jbuf_size = 4;
|
|
|
|
const uint16_t min_readiness_idx = 2; /* when is buffer ready to dqq */
|
2013-10-13 22:14:27 +08:00
|
|
|
|
2014-05-11 00:00:49 +08:00
|
|
|
int empty_queue(JitterBuffer *q)
|
2014-03-07 10:13:04 +08:00
|
|
|
{
|
|
|
|
while (q->size > 0) {
|
|
|
|
rtp_free_msg(NULL, q->queue[q->front]);
|
|
|
|
q->front++;
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-03-07 10:13:04 +08:00
|
|
|
if (q->front == q->capacity)
|
|
|
|
q->front = 0;
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-03-07 10:13:04 +08:00
|
|
|
q->size--;
|
|
|
|
}
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-03-07 10:13:04 +08:00
|
|
|
q->id_set = 0;
|
|
|
|
q->queue_ready = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2014-02-01 19:52:48 +08:00
|
|
|
|
2014-05-11 00:00:49 +08:00
|
|
|
JitterBuffer *create_queue(int capacity)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-05-11 00:00:49 +08:00
|
|
|
JitterBuffer *q;
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-11 00:00:49 +08:00
|
|
|
if ( !(q = calloc(sizeof(JitterBuffer), 1)) ) return NULL;
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-31 06:29:43 +08:00
|
|
|
if (!(q->queue = calloc(sizeof(RTPMessage *), capacity))) {
|
|
|
|
free(q);
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2013-10-13 22:14:27 +08:00
|
|
|
q->size = 0;
|
2014-06-22 01:04:00 +08:00
|
|
|
q->capacity = capacity >= min_jbuf_size ? capacity : min_jbuf_size;
|
2013-10-13 22:14:27 +08:00
|
|
|
q->front = 0;
|
|
|
|
q->rear = -1;
|
|
|
|
q->queue_ready = 0;
|
|
|
|
q->current_id = 0;
|
|
|
|
q->current_ts = 0;
|
|
|
|
q->id_set = 0;
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
2014-05-26 00:27:48 +08:00
|
|
|
void terminate_queue(JitterBuffer *q)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-05-17 01:56:40 +08:00
|
|
|
empty_queue(q);
|
2014-05-11 00:00:49 +08:00
|
|
|
free(q->queue);
|
|
|
|
free(q);
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
#define sequnum_older(sn_a, sn_b, ts_a, ts_b) (sn_a > sn_b || ts_a > ts_b)
|
2013-10-13 22:14:27 +08:00
|
|
|
|
|
|
|
/* success is 0 when there is nothing to dequeue, 1 when there's a good packet, 2 when there's a lost packet */
|
2014-05-11 00:00:49 +08:00
|
|
|
RTPMessage *dequeue(JitterBuffer *q, int *success)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-03-07 10:13:04 +08:00
|
|
|
if (q->size == 0 || q->queue_ready == 0) { /* Empty queue */
|
2013-10-13 22:14:27 +08:00
|
|
|
q->queue_ready = 0;
|
|
|
|
*success = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int front = q->front;
|
|
|
|
|
|
|
|
if (q->id_set == 0) {
|
2014-01-25 21:41:04 +08:00
|
|
|
q->current_id = q->queue[front]->header->sequnum;
|
|
|
|
q->current_ts = q->queue[front]->header->timestamp;
|
2013-10-13 22:14:27 +08:00
|
|
|
q->id_set = 1;
|
|
|
|
} else {
|
2014-01-25 21:41:04 +08:00
|
|
|
int next_id = q->queue[front]->header->sequnum;
|
|
|
|
int next_ts = q->queue[front]->header->timestamp;
|
2013-10-13 22:14:27 +08:00
|
|
|
|
|
|
|
/* if this packet is indeed the expected packet */
|
2014-01-25 21:41:04 +08:00
|
|
|
if (next_id == (q->current_id + 1) % MAX_SEQU_NUM) {
|
2013-10-13 22:14:27 +08:00
|
|
|
q->current_id = next_id;
|
|
|
|
q->current_ts = next_ts;
|
|
|
|
} else {
|
2014-05-03 07:46:03 +08:00
|
|
|
if (sequnum_older(next_id, q->current_id, next_ts, q->current_ts)) {
|
|
|
|
LOGGER_DEBUG("nextid: %d current: %d\n", next_id, q->current_id);
|
2014-01-25 21:41:04 +08:00
|
|
|
q->current_id = (q->current_id + 1) % MAX_SEQU_NUM;
|
2013-10-13 22:14:27 +08:00
|
|
|
*success = 2; /* tell the decoder the packet is lost */
|
|
|
|
return NULL;
|
|
|
|
} else {
|
2014-05-03 07:46:03 +08:00
|
|
|
LOGGER_DEBUG("Packet too old");
|
2013-10-13 22:14:27 +08:00
|
|
|
*success = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
q->size--;
|
|
|
|
q->front++;
|
|
|
|
|
|
|
|
if (q->front == q->capacity)
|
|
|
|
q->front = 0;
|
|
|
|
|
|
|
|
*success = 1;
|
2014-01-25 21:41:04 +08:00
|
|
|
q->current_id = q->queue[front]->header->sequnum;
|
|
|
|
q->current_ts = q->queue[front]->header->timestamp;
|
2013-10-13 22:14:27 +08:00
|
|
|
return q->queue[front];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-26 00:27:48 +08:00
|
|
|
void queue(JitterBuffer *q, RTPMessage *pk)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-03-07 10:13:04 +08:00
|
|
|
if (q->size == q->capacity) { /* Full, empty queue */
|
2014-05-11 00:00:49 +08:00
|
|
|
LOGGER_DEBUG("Queue full s(%d) c(%d), emptying...", q->size, q->capacity);
|
2013-10-13 22:14:27 +08:00
|
|
|
empty_queue(q);
|
|
|
|
}
|
|
|
|
|
2014-06-22 01:04:00 +08:00
|
|
|
if (q->size >= min_readiness_idx) q->queue_ready = 1;
|
2013-10-13 22:14:27 +08:00
|
|
|
|
|
|
|
++q->size;
|
|
|
|
++q->rear;
|
|
|
|
|
2014-06-22 01:04:00 +08:00
|
|
|
if (q->rear == q->capacity) q->rear = 0;
|
2013-10-13 22:14:27 +08:00
|
|
|
|
|
|
|
q->queue[q->rear] = pk;
|
|
|
|
|
|
|
|
int a;
|
|
|
|
int j;
|
|
|
|
a = q->rear;
|
|
|
|
|
|
|
|
for (j = 0; j < q->size - 1; ++j) {
|
2014-06-21 03:32:13 +08:00
|
|
|
int b = a - 1;
|
2013-10-13 22:14:27 +08:00
|
|
|
|
|
|
|
if (b < 0)
|
|
|
|
b += q->capacity;
|
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if (sequnum_older(q->queue[b]->header->sequnum, q->queue[a]->header->sequnum,
|
|
|
|
q->queue[b]->header->timestamp, q->queue[a]->header->timestamp)) {
|
2014-01-25 21:41:04 +08:00
|
|
|
RTPMessage *temp;
|
2013-10-13 22:14:27 +08:00
|
|
|
temp = q->queue[a];
|
|
|
|
q->queue[a] = q->queue[b];
|
|
|
|
q->queue[b] = temp;
|
2014-05-03 07:46:03 +08:00
|
|
|
LOGGER_DEBUG("Had to swap");
|
2013-10-13 22:14:27 +08:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
a -= 1;
|
|
|
|
|
2014-06-22 01:04:00 +08:00
|
|
|
if (a < 0) a += q->capacity;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
int init_video_decoder(CodecState *cs)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-05-03 07:46:03 +08:00
|
|
|
int rc = vpx_codec_dec_init_ver(&cs->v_decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0, VPX_DECODER_ABI_VERSION);
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if ( rc != VPX_CODEC_OK) {
|
|
|
|
LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc));
|
2014-02-10 06:06:44 +08:00
|
|
|
return -1;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
2014-02-16 03:44:33 +08:00
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2013-10-13 22:14:27 +08:00
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
int init_audio_decoder(CodecState *cs, uint32_t audio_channels)
|
|
|
|
{
|
|
|
|
int rc;
|
2014-02-17 09:01:30 +08:00
|
|
|
cs->audio_decoder = opus_decoder_create(cs->audio_sample_rate, audio_channels, &rc );
|
|
|
|
|
|
|
|
if ( rc != OPUS_OK ) {
|
2014-05-03 07:46:03 +08:00
|
|
|
LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc));
|
2014-02-10 06:06:44 +08:00
|
|
|
return -1;
|
2014-02-17 09:01:30 +08:00
|
|
|
}
|
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
return 0;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
|
2014-02-16 03:44:33 +08:00
|
|
|
int init_video_encoder(CodecState *cs, uint16_t width, uint16_t height, uint32_t video_bitrate)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-02-16 03:44:33 +08:00
|
|
|
vpx_codec_enc_cfg_t cfg;
|
2014-05-03 07:46:03 +08:00
|
|
|
int rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if (rc) {
|
|
|
|
LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc));
|
2014-02-10 06:06:44 +08:00
|
|
|
return -1;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-02-16 03:44:33 +08:00
|
|
|
cfg.rc_target_bitrate = video_bitrate;
|
|
|
|
cfg.g_w = width;
|
|
|
|
cfg.g_h = height;
|
2014-06-25 06:07:31 +08:00
|
|
|
cfg.g_pass = VPX_RC_ONE_PASS;
|
|
|
|
cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS;
|
|
|
|
cfg.g_lag_in_frames = 0;
|
|
|
|
cfg.kf_min_dist = 0;
|
|
|
|
cfg.kf_max_dist = 300;
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
rc = vpx_codec_enc_init_ver(&cs->v_encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0, VPX_ENCODER_ABI_VERSION);
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if ( rc != VPX_CODEC_OK) {
|
|
|
|
LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc));
|
2014-02-10 06:06:44 +08:00
|
|
|
return -1;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-06-25 06:07:31 +08:00
|
|
|
rc = vpx_codec_control(&cs->v_encoder, VP8E_SET_CPUUSED, 7);
|
2014-02-10 06:06:44 +08:00
|
|
|
return 0;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
|
|
|
|
2014-02-16 03:44:33 +08:00
|
|
|
int init_audio_encoder(CodecState *cs, uint32_t audio_channels)
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-05-03 07:46:03 +08:00
|
|
|
int rc = OPUS_OK;
|
|
|
|
cs->audio_encoder = opus_encoder_create(cs->audio_sample_rate, audio_channels, OPUS_APPLICATION_AUDIO, &rc);
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if ( rc != OPUS_OK ) {
|
|
|
|
LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc));
|
|
|
|
return -1;
|
|
|
|
}
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_BITRATE(cs->audio_bitrate));
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if ( rc != OPUS_OK ) {
|
|
|
|
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc));
|
|
|
|
return -1;
|
|
|
|
}
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10));
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
if ( rc != OPUS_OK ) {
|
|
|
|
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc));
|
|
|
|
return -1;
|
|
|
|
}
|
2014-02-17 09:01:30 +08:00
|
|
|
|
|
|
|
|
2014-05-03 07:46:03 +08:00
|
|
|
return 0;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
|
2014-02-17 09:01:30 +08:00
|
|
|
CodecState *codec_init_session ( uint32_t audio_bitrate,
|
|
|
|
uint16_t audio_frame_duration,
|
|
|
|
uint32_t audio_sample_rate,
|
|
|
|
uint32_t audio_channels,
|
2014-06-22 01:04:00 +08:00
|
|
|
uint32_t audio_VAD_tolerance_ms,
|
2014-02-16 03:44:33 +08:00
|
|
|
uint16_t video_width,
|
|
|
|
uint16_t video_height,
|
|
|
|
uint32_t video_bitrate )
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-03-07 10:13:04 +08:00
|
|
|
CodecState *retu = calloc(sizeof(CodecState), 1);
|
2014-05-26 00:27:48 +08:00
|
|
|
|
|
|
|
if (!retu) return NULL;
|
2014-02-16 03:44:33 +08:00
|
|
|
|
2014-03-07 10:13:04 +08:00
|
|
|
retu->audio_bitrate = audio_bitrate;
|
|
|
|
retu->audio_sample_rate = audio_sample_rate;
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-02-10 06:06:44 +08:00
|
|
|
/* Encoders */
|
2014-03-07 10:13:04 +08:00
|
|
|
if (!video_width || !video_height) { /* Disable video */
|
|
|
|
/*video_width = 320;
|
|
|
|
video_height = 240; */
|
2014-05-26 00:27:48 +08:00
|
|
|
} else {
|
2014-03-11 07:36:47 +08:00
|
|
|
retu->capabilities |= ( 0 == init_video_encoder(retu, video_width, video_height, video_bitrate) ) ? v_encoding : 0;
|
|
|
|
retu->capabilities |= ( 0 == init_video_decoder(retu) ) ? v_decoding : 0;
|
2014-02-16 03:44:33 +08:00
|
|
|
}
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-03-11 07:36:47 +08:00
|
|
|
retu->capabilities |= ( 0 == init_audio_encoder(retu, audio_channels) ) ? a_encoding : 0;
|
|
|
|
retu->capabilities |= ( 0 == init_audio_decoder(retu, audio_channels) ) ? a_decoding : 0;
|
2014-02-17 09:01:30 +08:00
|
|
|
|
2014-05-11 00:00:49 +08:00
|
|
|
if ( retu->capabilities == 0 ) { /* everything failed */
|
|
|
|
free (retu);
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-06-22 20:39:26 +08:00
|
|
|
|
|
|
|
retu->EVAD_tolerance = audio_VAD_tolerance_ms > audio_frame_duration ?
|
|
|
|
audio_VAD_tolerance_ms / audio_frame_duration : audio_frame_duration;
|
|
|
|
|
2014-03-07 10:13:04 +08:00
|
|
|
return retu;
|
2013-10-13 22:14:27 +08:00
|
|
|
}
|
|
|
|
|
2014-02-17 09:01:30 +08:00
|
|
|
void codec_terminate_session ( CodecState *cs )
|
2013-10-13 22:14:27 +08:00
|
|
|
{
|
2014-05-26 00:27:48 +08:00
|
|
|
if ( cs->audio_encoder )
|
2014-02-10 06:06:44 +08:00
|
|
|
opus_encoder_destroy(cs->audio_encoder);
|
2014-05-26 00:27:48 +08:00
|
|
|
|
|
|
|
if ( cs->audio_decoder )
|
2014-02-10 06:06:44 +08:00
|
|
|
opus_decoder_destroy(cs->audio_decoder);
|
2014-02-16 03:44:33 +08:00
|
|
|
|
2014-03-11 07:36:47 +08:00
|
|
|
if ( cs->capabilities & v_decoding )
|
2014-03-07 10:13:04 +08:00
|
|
|
vpx_codec_destroy(&cs->v_decoder);
|
2014-05-26 00:27:48 +08:00
|
|
|
|
2014-03-11 07:36:47 +08:00
|
|
|
if ( cs->capabilities & v_encoding )
|
2014-03-07 10:13:04 +08:00
|
|
|
vpx_codec_destroy(&cs->v_encoder);
|
2014-02-10 06:06:44 +08:00
|
|
|
}
|
2014-06-21 07:58:55 +08:00
|
|
|
|
2014-06-22 20:39:26 +08:00
|
|
|
inline float calculate_sum_sq (int16_t *n, uint16_t k)
|
2014-06-21 07:58:55 +08:00
|
|
|
{
|
|
|
|
float result = 0;
|
2014-06-22 01:04:00 +08:00
|
|
|
uint16_t i = 0;
|
2014-06-22 20:39:26 +08:00
|
|
|
|
2014-06-22 01:04:00 +08:00
|
|
|
for ( ; i < k; i ++) result += (float) (n[i] * n[i]);
|
2014-06-22 20:39:26 +08:00
|
|
|
|
2014-06-21 07:58:55 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-06-22 20:39:26 +08:00
|
|
|
int energy_VAD(CodecState *cs, int16_t *PCM, uint16_t frame_size, float energy)
|
2014-06-21 07:58:55 +08:00
|
|
|
{
|
|
|
|
float frame_energy = sqrt(calculate_sum_sq(PCM, frame_size)) / frame_size;
|
2014-06-22 20:39:26 +08:00
|
|
|
|
|
|
|
if ( frame_energy > energy) {
|
2014-06-22 01:04:00 +08:00
|
|
|
cs->EVAD_tolerance_cr = cs->EVAD_tolerance; /* Reset counter */
|
|
|
|
return 1;
|
|
|
|
}
|
2014-06-22 20:39:26 +08:00
|
|
|
|
|
|
|
if ( cs->EVAD_tolerance_cr ) {
|
|
|
|
cs->EVAD_tolerance_cr --;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-06-21 07:58:55 +08:00
|
|
|
return 0;
|
2014-06-22 08:41:32 +08:00
|
|
|
}
|