blob: 82893f5fc511651b2a266c58486e5dbc5a475457 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id: audiotest.c 3553 2011-05-05 06:14:19Z nanang $ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjmedia-audiodev/audiotest.h>
21#include <pjmedia-audiodev/audiodev.h>
22#include <pjlib.h>
23#include <pjlib-util.h>
24
25#define THIS_FILE "audiotest.c"
26
27/* Test duration in msec */
28#define DURATION 10000
29
30/* Skip the first msec from the calculation */
31#define SKIP_DURATION 1000
32
33/* Division helper */
34#define DIV_ROUND_UP(a,b) (((a) + ((b) - 1)) / (b))
35#define DIV_ROUND(a,b) (((a) + ((b)/2 - 1)) / (b))
36
37struct stream_data
38{
39 pj_uint32_t first_timestamp;
40 pj_uint32_t last_timestamp;
41 pj_timestamp last_called;
42 pj_math_stat delay;
43};
44
45struct test_data
46{
47 pj_pool_t *pool;
48 const pjmedia_aud_param *param;
49 pjmedia_aud_test_results *result;
50 pj_bool_t running;
51 pj_bool_t has_error;
52 pj_mutex_t *mutex;
53
54 struct stream_data capture_data;
55 struct stream_data playback_data;
56};
57
58static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
59{
60 struct test_data *test_data = (struct test_data *)user_data;
61 struct stream_data *strm_data = &test_data->playback_data;
62
63 pj_mutex_lock(test_data->mutex);
64
65 /* Skip frames when test is not started or test has finished */
66 if (!test_data->running) {
67 pj_bzero(frame->buf, frame->size);
68 pj_mutex_unlock(test_data->mutex);
69 return PJ_SUCCESS;
70 }
71
72 /* Save last timestamp seen (to calculate drift) */
73 strm_data->last_timestamp = frame->timestamp.u32.lo;
74
75 if (strm_data->last_called.u64 == 0) {
76 /* Init vars. */
77 pj_get_timestamp(&strm_data->last_called);
78 pj_math_stat_init(&strm_data->delay);
79 strm_data->first_timestamp = frame->timestamp.u32.lo;
80 } else {
81 pj_timestamp now;
82 unsigned delay;
83
84 /* Calculate frame interval */
85 pj_get_timestamp(&now);
86 delay = pj_elapsed_usec(&strm_data->last_called, &now);
87 strm_data->last_called = now;
88
89 /* Update frame interval statistic */
90 pj_math_stat_update(&strm_data->delay, delay);
91 }
92
93 pj_bzero(frame->buf, frame->size);
94
95 pj_mutex_unlock(test_data->mutex);
96
97 return PJ_SUCCESS;
98}
99
100static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
101{
102 struct test_data *test_data = (struct test_data*)user_data;
103 struct stream_data *strm_data = &test_data->capture_data;
104
105 pj_mutex_lock(test_data->mutex);
106
107 /* Skip frames when test is not started or test has finished */
108 if (!test_data->running) {
109 pj_mutex_unlock(test_data->mutex);
110 return PJ_SUCCESS;
111 }
112
113 /* Save last timestamp seen (to calculate drift) */
114 strm_data->last_timestamp = frame->timestamp.u32.lo;
115
116 if (strm_data->last_called.u64 == 0) {
117 /* Init vars. */
118 pj_get_timestamp(&strm_data->last_called);
119 pj_math_stat_init(&strm_data->delay);
120 strm_data->first_timestamp = frame->timestamp.u32.lo;
121 } else {
122 pj_timestamp now;
123 unsigned delay;
124
125 /* Calculate frame interval */
126 pj_get_timestamp(&now);
127 delay = pj_elapsed_usec(&strm_data->last_called, &now);
128 strm_data->last_called = now;
129
130 /* Update frame interval statistic */
131 pj_math_stat_update(&strm_data->delay, delay);
132 }
133
134 pj_mutex_unlock(test_data->mutex);
135 return PJ_SUCCESS;
136}
137
138static void app_perror(const char *title, pj_status_t status)
139{
140 char errmsg[PJ_ERR_MSG_SIZE];
141
142 pj_strerror(status, errmsg, sizeof(errmsg));
143 printf( "%s: %s (err=%d)\n",
144 title, errmsg, status);
145}
146
147
148PJ_DEF(pj_status_t) pjmedia_aud_test( const pjmedia_aud_param *param,
149 pjmedia_aud_test_results *result)
150{
151 pj_status_t status = PJ_SUCCESS;
152 pjmedia_aud_stream *strm;
153 struct test_data test_data;
154 unsigned ptime, tmp;
155
156 /*
157 * Init test parameters
158 */
159 pj_bzero(&test_data, sizeof(test_data));
160 test_data.param = param;
161 test_data.result = result;
162
163 test_data.pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
164 "audtest", 1000, 1000, NULL);
165 pj_mutex_create_simple(test_data.pool, "sndtest", &test_data.mutex);
166
167 /*
168 * Open device.
169 */
170 status = pjmedia_aud_stream_create(test_data.param, &rec_cb, &play_cb,
171 &test_data, &strm);
172 if (status != PJ_SUCCESS) {
173 app_perror("Unable to open device", status);
174 pj_pool_release(test_data.pool);
175 return status;
176 }
177
178
179 /* Sleep for a while to let sound device "settles" */
180 pj_thread_sleep(200);
181
182 /*
183 * Start the stream.
184 */
185 status = pjmedia_aud_stream_start(strm);
186 if (status != PJ_SUCCESS) {
187 app_perror("Unable to start capture stream", status);
188 pjmedia_aud_stream_destroy(strm);
189 pj_pool_release(test_data.pool);
190 return status;
191 }
192
193 PJ_LOG(3,(THIS_FILE,
194 " Please wait while test is in progress (~%d secs)..",
195 (DURATION+SKIP_DURATION)/1000));
196
197 /* Let the stream runs for few msec/sec to get stable result.
198 * (capture normally begins with frames available simultaneously).
199 */
200 pj_thread_sleep(SKIP_DURATION);
201
202
203 /* Begin gather data */
204 test_data.running = 1;
205
206 /*
207 * Let the test runs for a while.
208 */
209 pj_thread_sleep(DURATION);
210
211
212 /*
213 * Close stream.
214 */
215 test_data.running = 0;
216 pjmedia_aud_stream_destroy(strm);
217 pj_pool_release(test_data.pool);
218
219
220 /*
221 * Gather results
222 */
223 ptime = param->samples_per_frame * 1000 / param->clock_rate;
224
225 tmp = pj_math_stat_get_stddev(&test_data.capture_data.delay);
226 result->rec.frame_cnt = test_data.capture_data.delay.n;
227 result->rec.min_interval = DIV_ROUND(test_data.capture_data.delay.min, 1000);
228 result->rec.max_interval = DIV_ROUND(test_data.capture_data.delay.max, 1000);
229 result->rec.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000);
230 result->rec.dev_interval = DIV_ROUND(tmp, 1000);
231 result->rec.max_burst = DIV_ROUND_UP(result->rec.max_interval, ptime);
232
233 tmp = pj_math_stat_get_stddev(&test_data.playback_data.delay);
234 result->play.frame_cnt = test_data.playback_data.delay.n;
235 result->play.min_interval = DIV_ROUND(test_data.playback_data.delay.min, 1000);
236 result->play.max_interval = DIV_ROUND(test_data.playback_data.delay.max, 1000);
237 result->play.avg_interval = DIV_ROUND(test_data.playback_data.delay.mean, 1000);
238 result->play.dev_interval = DIV_ROUND(tmp, 1000);
239 result->play.max_burst = DIV_ROUND_UP(result->play.max_interval, ptime);
240
241 /* Check drifting */
242 if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) {
243 int play_diff, cap_diff, drift;
244
245 play_diff = test_data.playback_data.last_timestamp -
246 test_data.playback_data.first_timestamp;
247 cap_diff = test_data.capture_data.last_timestamp -
248 test_data.capture_data.first_timestamp;
249 drift = play_diff > cap_diff? play_diff - cap_diff :
250 cap_diff - play_diff;
251
252 /* Allow one frame tolerance for clock drift detection */
253 if (drift < (int)param->samples_per_frame) {
254 result->rec_drift_per_sec = 0;
255 } else {
256 unsigned msec_dur;
257
258 msec_dur = (test_data.capture_data.last_timestamp -
259 test_data.capture_data.first_timestamp) * 1000 /
260 test_data.param->clock_rate;
261
262 result->rec_drift_per_sec = drift * 1000 / msec_dur;
263
264 }
265 }
266
267 return test_data.has_error? PJ_EUNKNOWN : PJ_SUCCESS;
268}
269