blob: 255663a41060ca21f26d817c1e438888c976beda [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id$ */
2/*
3 * Copyright (C) 2012-2012 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
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/* This file is the implementation of Android JNI audio device.
21 * The original code was originally part of CSipSimple
22 * (http://code.google.com/p/csipsimple/) and was kindly donated
23 * by Regis Montoya.
24 */
25
26#include <pjmedia-audiodev/audiodev_imp.h>
27#include <pj/assert.h>
28#include <pj/log.h>
29#include <pj/os.h>
30#include <pj/string.h>
31#include <pjmedia/errno.h>
32
33#if defined(PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI) && \
34 PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI != 0
35
36#include <jni.h>
37#include <sys/resource.h>
38#include <sys/system_properties.h>
39
40#define THIS_FILE "android_jni_dev.c"
41#define DRIVER_NAME "Android JNI"
42
43struct android_aud_factory
44{
45 pjmedia_aud_dev_factory base;
46 pj_pool_factory *pf;
47 pj_pool_t *pool;
48};
49
50/*
51 * Sound stream descriptor.
52 * This struct may be used for both unidirectional or bidirectional sound
53 * streams.
54 */
55struct android_aud_stream
56{
57 pjmedia_aud_stream base;
58 pj_pool_t *pool;
59 pj_str_t name;
60 pjmedia_dir dir;
61 pjmedia_aud_param param;
62
63 int bytes_per_sample;
64 pj_uint32_t samples_per_sec;
65 unsigned samples_per_frame;
66 int channel_count;
67 void *user_data;
68 pj_bool_t quit_flag;
69 pj_bool_t running;
70
71 /* Record */
72 jobject record;
73 jclass record_class;
74 unsigned rec_buf_size;
75 pjmedia_aud_rec_cb rec_cb;
76 pj_bool_t rec_thread_exited;
77 pj_thread_t *rec_thread;
78 pj_sem_t *rec_sem;
79 pj_timestamp rec_timestamp;
80
81 /* Track */
82 jobject track;
83 jclass track_class;
84 unsigned play_buf_size;
85 pjmedia_aud_play_cb play_cb;
86 pj_bool_t play_thread_exited;
87 pj_thread_t *play_thread;
88 pj_sem_t *play_sem;
89 pj_timestamp play_timestamp;
90};
91
92/* Factory prototypes */
93static pj_status_t android_init(pjmedia_aud_dev_factory *f);
94static pj_status_t android_destroy(pjmedia_aud_dev_factory *f);
95static pj_status_t android_refresh(pjmedia_aud_dev_factory *f);
96static unsigned android_get_dev_count(pjmedia_aud_dev_factory *f);
97static pj_status_t android_get_dev_info(pjmedia_aud_dev_factory *f,
98 unsigned index,
99 pjmedia_aud_dev_info *info);
100static pj_status_t android_default_param(pjmedia_aud_dev_factory *f,
101 unsigned index,
102 pjmedia_aud_param *param);
103static pj_status_t android_create_stream(pjmedia_aud_dev_factory *f,
104 const pjmedia_aud_param *param,
105 pjmedia_aud_rec_cb rec_cb,
106 pjmedia_aud_play_cb play_cb,
107 void *user_data,
108 pjmedia_aud_stream **p_aud_strm);
109
110/* Stream prototypes */
111static pj_status_t strm_get_param(pjmedia_aud_stream *strm,
112 pjmedia_aud_param *param);
113static pj_status_t strm_get_cap(pjmedia_aud_stream *strm,
114 pjmedia_aud_dev_cap cap,
115 void *value);
116static pj_status_t strm_set_cap(pjmedia_aud_stream *strm,
117 pjmedia_aud_dev_cap cap,
118 const void *value);
119static pj_status_t strm_start(pjmedia_aud_stream *strm);
120static pj_status_t strm_stop(pjmedia_aud_stream *strm);
121static pj_status_t strm_destroy(pjmedia_aud_stream *strm);
122
123static pjmedia_aud_dev_factory_op android_op =
124{
125 &android_init,
126 &android_destroy,
127 &android_get_dev_count,
128 &android_get_dev_info,
129 &android_default_param,
130 &android_create_stream,
131 &android_refresh
132};
133
134static pjmedia_aud_stream_op android_strm_op =
135{
136 &strm_get_param,
137 &strm_get_cap,
138 &strm_set_cap,
139 &strm_start,
140 &strm_stop,
141 &strm_destroy
142};
143
144JavaVM *android_jvm;
145
146JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
147{
148 android_jvm = vm;
149
150 return JNI_VERSION_1_4;
151}
152
153static pj_bool_t attach_jvm(JNIEnv **jni_env)
154{
155 if ((*android_jvm)->GetEnv(android_jvm, (void **)jni_env,
156 JNI_VERSION_1_4) < 0)
157 {
158 if ((*android_jvm)->AttachCurrentThread(android_jvm, jni_env, NULL) < 0)
159 {
160 jni_env = NULL;
161 return PJ_FALSE;
162 }
163 return PJ_TRUE;
164 }
165
166 return PJ_FALSE;
167}
168
169#define detach_jvm(attached) \
170 if (attached) \
171 (*android_jvm)->DetachCurrentThread(android_jvm);
172
173/* Thread priority utils */
174/* TODO : port it to pj_thread functions */
175#define THREAD_PRIORITY_AUDIO -16
176#define THREAD_PRIORITY_URGENT_AUDIO -19
177
178pj_status_t set_android_thread_priority(int priority)
179{
180 jclass process_class;
181 jmethodID set_prio_method;
182 jthrowable exc;
183 pj_status_t result = PJ_SUCCESS;
184 JNIEnv *jni_env = 0;
185 pj_bool_t attached = attach_jvm(&jni_env);
186
187 PJ_ASSERT_RETURN(jni_env, PJ_FALSE);
188
189 /* Get pointer to the java class */
190 process_class = (jclass)(*jni_env)->NewGlobalRef(jni_env,
191 (*jni_env)->FindClass(jni_env, "android/os/Process"));
192 if (process_class == 0) {
193 PJ_LOG(4, (THIS_FILE, "Unable to find os process class"));
194 result = PJ_EIGNORED;
195 goto on_return;
196 }
197
198 /* Get the id of set thread priority function */
199 set_prio_method = (*jni_env)->GetStaticMethodID(jni_env, process_class,
200 "setThreadPriority",
201 "(I)V");
202 if (set_prio_method == 0) {
203 PJ_LOG(4, (THIS_FILE, "Unable to find setThreadPriority() method"));
204 result = PJ_EIGNORED;
205 goto on_return;
206 }
207
208 /* Set the thread priority */
209 (*jni_env)->CallStaticVoidMethod(jni_env, process_class, set_prio_method,
210 priority);
211 exc = (*jni_env)->ExceptionOccurred(jni_env);
212 if (exc) {
213 (*jni_env)->ExceptionDescribe(jni_env);
214 (*jni_env)->ExceptionClear(jni_env);
215 PJ_LOG(4, (THIS_FILE, "Failure in setting thread priority using "
216 "Java API, fallback to setpriority()"));
217 setpriority(PRIO_PROCESS, 0, priority);
218 } else {
219 PJ_LOG(4, (THIS_FILE, "Setting thread priority successful"));
220 }
221
222on_return:
223 detach_jvm(attached);
224 return result;
225}
226
227
228static int AndroidRecorderCallback(void *userData)
229{
230 struct android_aud_stream *stream = (struct android_aud_stream *)userData;
231 jmethodID read_method=0, record_method=0, stop_method=0;
232 int size = stream->rec_buf_size;
233 jbyteArray inputBuffer;
234 jbyte *buf;
235 JNIEnv *jni_env = 0;
236 pj_bool_t attached = attach_jvm(&jni_env);
237
238 PJ_ASSERT_RETURN(jni_env, 0);
239
240 if (!stream->record) {
241 goto on_return;
242 }
243
244 PJ_LOG(5, (THIS_FILE, "Recorder thread started"));
245
246 /* Get methods ids */
247 read_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
248 "read", "([BII)I");
249 record_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
250 "startRecording", "()V");
251 stop_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
252 "stop", "()V");
253 if (read_method==0 || record_method==0 || stop_method==0) {
254 PJ_LOG(3, (THIS_FILE, "Unable to get recording methods"));
255 goto on_return;
256 }
257
258 /* Create a buffer for frames read */
259 inputBuffer = (*jni_env)->NewByteArray(jni_env, size);
260 if (inputBuffer == 0) {
261 PJ_LOG(3, (THIS_FILE, "Unable to allocate input buffer"));
262 goto on_return;
263 }
264 buf = (*jni_env)->GetByteArrayElements(jni_env, inputBuffer, 0);
265
266 /* Start recording
267 * setpriority(PRIO_PROCESS, 0, -19); //ANDROID_PRIORITY_AUDIO
268 * set priority is probably not enough because it does not change the thread
269 * group in scheduler
270 * Temporary solution is to call the java api to set the thread priority.
271 * A cool solution would be to port (if possible) the code from the
272 * android os regarding set_sched groups
273 */
274 set_android_thread_priority(THREAD_PRIORITY_URGENT_AUDIO);
275 (*jni_env)->CallVoidMethod(jni_env, stream->record, record_method);
276
277 while (!stream->quit_flag) {
278 pjmedia_frame frame;
279 pj_status_t status;
280 int bytesRead;
281
282 if (!stream->running) {
283 (*jni_env)->CallVoidMethod(jni_env, stream->record, stop_method);
284 pj_sem_wait(stream->rec_sem);
285 if (stream->quit_flag)
286 break;
287 (*jni_env)->CallVoidMethod(jni_env, stream->record, record_method);
288 }
289
290 bytesRead = (*jni_env)->CallIntMethod(jni_env, stream->record,
291 read_method, inputBuffer,
292 0, size);
293 if (bytesRead <= 0 || bytesRead != size) {
294 PJ_LOG (4, (THIS_FILE, "Record thread : error %d reading data",
295 bytesRead));
296 continue;
297 }
298
299 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
300 frame.size = size;
301 frame.bit_info = 0;
302 frame.buf = (void *)buf;
303 frame.timestamp.u64 = stream->rec_timestamp.u64;
304
305 status = (*stream->rec_cb)(stream->user_data, &frame);
306
307 stream->rec_timestamp.u64 += stream->param.samples_per_frame /
308 stream->param.channel_count;
309 }
310
311 (*jni_env)->ReleaseByteArrayElements(jni_env, inputBuffer, buf, 0);
312 (*jni_env)->DeleteLocalRef(jni_env, inputBuffer);
313
314on_return:
315 detach_jvm(attached);
316 PJ_LOG(5, (THIS_FILE, "Recorder thread stopped"));
317 stream->rec_thread_exited = 1;
318
319 return 0;
320}
321
322
323static int AndroidTrackCallback(void *userData)
324{
325 struct android_aud_stream *stream = (struct android_aud_stream*) userData;
326 jmethodID write_method=0, play_method=0, stop_method=0, flush_method=0;
327 int size = stream->play_buf_size;
328 jbyteArray outputBuffer;
329 jbyte *buf;
330 JNIEnv *jni_env = 0;
331 pj_bool_t attached = attach_jvm(&jni_env);
332
333 if (!stream->track) {
334 goto on_return;
335 }
336
337 PJ_LOG(5, (THIS_FILE, "Playback thread started"));
338
339 /* Get methods ids */
340 write_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
341 "write", "([BII)I");
342 play_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
343 "play", "()V");
344 stop_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
345 "stop", "()V");
346 flush_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
347 "flush", "()V");
348 if (write_method==0 || play_method==0 || stop_method==0 ||
349 flush_method==0)
350 {
351 PJ_LOG(3, (THIS_FILE, "Unable to get audio track methods"));
352 goto on_return;
353 }
354
355 outputBuffer = (*jni_env)->NewByteArray(jni_env, size);
356 if (outputBuffer == 0) {
357 PJ_LOG(3, (THIS_FILE, "Unable to allocate output buffer"));
358 goto on_return;
359 }
360 buf = (*jni_env)->GetByteArrayElements(jni_env, outputBuffer, 0);
361
362 /* Start playing */
363 set_android_thread_priority(THREAD_PRIORITY_URGENT_AUDIO);
364 (*jni_env)->CallVoidMethod(jni_env, stream->track, play_method);
365
366 while (!stream->quit_flag) {
367 pjmedia_frame frame;
368 pj_status_t status;
369 int bytesWritten;
370
371 if (!stream->running) {
372 (*jni_env)->CallVoidMethod(jni_env, stream->track, stop_method);
373 (*jni_env)->CallVoidMethod(jni_env, stream->track, flush_method);
374 pj_sem_wait(stream->play_sem);
375 if (stream->quit_flag)
376 break;
377 (*jni_env)->CallVoidMethod(jni_env, stream->track, play_method);
378 }
379
380 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
381 frame.size = size;
382 frame.buf = (void *)buf;
383 frame.timestamp.u64 = stream->play_timestamp.u64;
384 frame.bit_info = 0;
385
386 status = (*stream->play_cb)(stream->user_data, &frame);
387 if (status != PJ_SUCCESS)
388 continue;
389
390 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
391 pj_bzero(frame.buf, frame.size);
392
393 /* Write to the device output. */
394 bytesWritten = (*jni_env)->CallIntMethod(jni_env, stream->track,
395 write_method, outputBuffer,
396 0, size);
397 if (bytesWritten <= 0 || bytesWritten != size) {
398 PJ_LOG(4, (THIS_FILE, "Player thread: Error %d writing data",
399 bytesWritten));
400 continue;
401 }
402
403 stream->play_timestamp.u64 += stream->param.samples_per_frame /
404 stream->param.channel_count;
405 };
406
407 (*jni_env)->ReleaseByteArrayElements(jni_env, outputBuffer, buf, 0);
408 (*jni_env)->DeleteLocalRef(jni_env, outputBuffer);
409
410on_return:
411 detach_jvm(attached);
412 PJ_LOG(5, (THIS_FILE, "Player thread stopped"));
413 stream->play_thread_exited = 1;
414
415 return 0;
416}
417
418/*
419 * Init Android audio driver.
420 */
421pjmedia_aud_dev_factory* pjmedia_android_factory(pj_pool_factory *pf)
422{
423 struct android_aud_factory *f;
424 pj_pool_t *pool;
425
426 pool = pj_pool_create(pf, "androidjni", 256, 256, NULL);
427 f = PJ_POOL_ZALLOC_T(pool, struct android_aud_factory);
428 f->pf = pf;
429 f->pool = pool;
430 f->base.op = &android_op;
431
432 return &f->base;
433}
434
435/* API: Init factory */
436static pj_status_t android_init(pjmedia_aud_dev_factory *f)
437{
438 PJ_UNUSED_ARG(f);
439
440 PJ_LOG(4, (THIS_FILE, "Android JNI sound library initialized"));
441
442 return PJ_SUCCESS;
443}
444
445
446/* API: refresh the list of devices */
447static pj_status_t android_refresh(pjmedia_aud_dev_factory *f)
448{
449 PJ_UNUSED_ARG(f);
450 return PJ_SUCCESS;
451}
452
453
454/* API: Destroy factory */
455static pj_status_t android_destroy(pjmedia_aud_dev_factory *f)
456{
457 struct android_aud_factory *pa = (struct android_aud_factory*)f;
458 pj_pool_t *pool;
459
460 PJ_LOG(4, (THIS_FILE, "Android JNI sound library shutting down.."));
461
462 pool = pa->pool;
463 pa->pool = NULL;
464 pj_pool_release(pool);
465
466 return PJ_SUCCESS;
467}
468
469/* API: Get device count. */
470static unsigned android_get_dev_count(pjmedia_aud_dev_factory *f)
471{
472 PJ_UNUSED_ARG(f);
473 return 1;
474}
475
476/* API: Get device info. */
477static pj_status_t android_get_dev_info(pjmedia_aud_dev_factory *f,
478 unsigned index,
479 pjmedia_aud_dev_info *info)
480{
481 PJ_UNUSED_ARG(f);
482
483 pj_bzero(info, sizeof(*info));
484
485 pj_ansi_strcpy(info->name, "Android JNI");
486 info->default_samples_per_sec = 8000;
487 info->caps = PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
488 info->input_count = 1;
489 info->output_count = 1;
490
491 return PJ_SUCCESS;
492}
493
494/* API: fill in with default parameter. */
495static pj_status_t android_default_param(pjmedia_aud_dev_factory *f,
496 unsigned index,
497 pjmedia_aud_param *param)
498{
499 pjmedia_aud_dev_info adi;
500 pj_status_t status;
501
502 status = android_get_dev_info(f, index, &adi);
503 if (status != PJ_SUCCESS)
504 return status;
505
506 pj_bzero(param, sizeof(*param));
507 if (adi.input_count && adi.output_count) {
508 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
509 param->rec_id = index;
510 param->play_id = index;
511 } else if (adi.input_count) {
512 param->dir = PJMEDIA_DIR_CAPTURE;
513 param->rec_id = index;
514 param->play_id = PJMEDIA_AUD_INVALID_DEV;
515 } else if (adi.output_count) {
516 param->dir = PJMEDIA_DIR_PLAYBACK;
517 param->play_id = index;
518 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
519 } else {
520 return PJMEDIA_EAUD_INVDEV;
521 }
522
523 param->clock_rate = adi.default_samples_per_sec;
524 param->channel_count = 1;
525 param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
526 param->bits_per_sample = 16;
527 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
528 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
529
530 return PJ_SUCCESS;
531}
532
533/* API: create stream */
534static pj_status_t android_create_stream(pjmedia_aud_dev_factory *f,
535 const pjmedia_aud_param *param,
536 pjmedia_aud_rec_cb rec_cb,
537 pjmedia_aud_play_cb play_cb,
538 void *user_data,
539 pjmedia_aud_stream **p_aud_strm)
540{
541 struct android_aud_factory *pa = (struct android_aud_factory*)f;
542 pj_pool_t *pool;
543 struct android_aud_stream *stream;
544 pj_status_t status = PJ_SUCCESS;
545 int state = 0;
546 int buffSize, inputBuffSizePlay, inputBuffSizeRec;
547 int channelInCfg, channelOutCfg, sampleFormat;
548 jmethodID constructor_method=0, bufsize_method = 0;
549 jmethodID method_id = 0;
550 jclass jcl;
551 JNIEnv *jni_env = 0;
552 pj_bool_t attached;
553
554 PJ_ASSERT_RETURN(param->channel_count >= 1 && param->channel_count <= 2,
555 PJ_EINVAL);
556 PJ_ASSERT_RETURN(param->bits_per_sample==8 || param->bits_per_sample==16,
557 PJ_EINVAL);
558 PJ_ASSERT_RETURN(play_cb && rec_cb && p_aud_strm, PJ_EINVAL);
559
560 pool = pj_pool_create(pa->pf, "jnistrm", 1024, 1024, NULL);
561 if (!pool)
562 return PJ_ENOMEM;
563
564 PJ_LOG(4, (THIS_FILE, "Creating Android JNI stream"));
565
566 stream = PJ_POOL_ZALLOC_T(pool, struct android_aud_stream);
567 stream->pool = pool;
568 pj_strdup2_with_null(pool, &stream->name, "JNI stream");
569 stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
570 pj_memcpy(&stream->param, param, sizeof(*param));
571 stream->user_data = user_data;
572 stream->rec_cb = rec_cb;
573 stream->play_cb = play_cb;
574 buffSize = stream->param.samples_per_frame*stream->param.bits_per_sample/8;
575 stream->rec_buf_size = stream->play_buf_size = buffSize;
576 channelInCfg = (param->channel_count == 1)? 16 /*CHANNEL_IN_MONO*/:
577 12 /*CHANNEL_IN_STEREO*/;
578 channelOutCfg = (param->channel_count == 1)? 4 /*CHANNEL_OUT_MONO*/:
579 12 /*CHANNEL_OUT_STEREO*/;
580 sampleFormat = (param->bits_per_sample == 8)? 3 /*ENCODING_PCM_8BIT*/:
581 2 /*ENCODING_PCM_16BIT*/;
582
583 attached = attach_jvm(&jni_env);
584
585 if (stream->dir & PJMEDIA_DIR_CAPTURE) {
586 /* Find audio record class and create global ref */
587 jcl = (*jni_env)->FindClass(jni_env, "android/media/AudioRecord");
588 if (jcl == NULL) {
589 PJ_LOG(3, (THIS_FILE, "Unable to find audio record class"));
590 status = PJMEDIA_EAUD_SYSERR;
591 goto on_error;
592 }
593 stream->record_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, jcl);
594 (*jni_env)->DeleteLocalRef(jni_env, jcl);
595 if (stream->record_class == 0) {
596 status = PJ_ENOMEM;
597 goto on_error;
598 }
599
600 /* Get the min buffer size function */
601 bufsize_method = (*jni_env)->GetStaticMethodID(jni_env,
602 stream->record_class,
603 "getMinBufferSize",
604 "(III)I");
605 if (bufsize_method == 0) {
606 PJ_LOG(3, (THIS_FILE, "Unable to find audio record "
607 "getMinBufferSize() method"));
608 status = PJMEDIA_EAUD_SYSERR;
609 goto on_error;
610 }
611
612 inputBuffSizeRec = (*jni_env)->CallStaticIntMethod(jni_env,
613 stream->record_class,
614 bufsize_method,
615 param->clock_rate,
616 channelInCfg,
617 sampleFormat);
618 if (inputBuffSizeRec <= 0) {
619 PJ_LOG(3, (THIS_FILE, "Unsupported audio record params"));
620 status = PJMEDIA_EAUD_INIT;
621 goto on_error;
622 }
623 }
624
625 if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
626 /* Find audio track class and create global ref */
627 jcl = (*jni_env)->FindClass(jni_env, "android/media/AudioTrack");
628 if (jcl == NULL) {
629 PJ_LOG(3, (THIS_FILE, "Unable to find audio track class"));
630 status = PJMEDIA_EAUD_SYSERR;
631 goto on_error;
632 }
633 stream->track_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, jcl);
634 (*jni_env)->DeleteLocalRef(jni_env, jcl);
635 if (stream->track_class == 0) {
636 status = PJ_ENOMEM;
637 goto on_error;
638 }
639
640 /* Get the min buffer size function */
641 bufsize_method = (*jni_env)->GetStaticMethodID(jni_env,
642 stream->track_class,
643 "getMinBufferSize",
644 "(III)I");
645 if (bufsize_method == 0) {
646 PJ_LOG(3, (THIS_FILE, "Unable to find audio track "
647 "getMinBufferSize() method"));
648 status = PJMEDIA_EAUD_SYSERR;
649 goto on_error;
650 }
651
652 inputBuffSizePlay = (*jni_env)->CallStaticIntMethod(jni_env,
653 stream->track_class,
654 bufsize_method,
655 param->clock_rate,
656 channelOutCfg,
657 sampleFormat);
658 if (inputBuffSizePlay <= 0) {
659 PJ_LOG(3, (THIS_FILE, "Unsupported audio track params"));
660 status = PJMEDIA_EAUD_INIT;
661 goto on_error;
662 }
663 }
664
665 if (stream->dir & PJMEDIA_DIR_CAPTURE) {
666 jthrowable exc;
667 int mic_source = 0; /* DEFAULT: default audio source */
668
669 /* Get pointer to the constructor */
670 constructor_method = (*jni_env)->GetMethodID(jni_env,
671 stream->record_class,
672 "<init>", "(IIIII)V");
673 if (constructor_method == 0) {
674 PJ_LOG(3, (THIS_FILE, "Unable to find audio record's constructor"));
675 status = PJMEDIA_EAUD_SYSERR;
676 goto on_error;
677 }
678
679 if (mic_source == 0) {
680 char sdk_version[PROP_VALUE_MAX];
681 pj_str_t pj_sdk_version;
682 int sdk_v;
683
684 __system_property_get("ro.build.version.sdk", sdk_version);
685 pj_sdk_version = pj_str(sdk_version);
686 sdk_v = pj_strtoul(&pj_sdk_version);
687 if (sdk_v > 10)
688 mic_source = 7; /* VOICE_COMMUNICATION */
689 }
690 PJ_LOG(4, (THIS_FILE, "Using audio input source : %d", mic_source));
691
692 do {
693 stream->record = (*jni_env)->NewObject(jni_env,
694 stream->record_class,
695 constructor_method,
696 mic_source,
697 param->clock_rate,
698 channelInCfg,
699 sampleFormat,
700 inputBuffSizeRec);
701 if (stream->record == 0) {
702 PJ_LOG(3, (THIS_FILE, "Unable to create audio record object"));
703 status = PJMEDIA_EAUD_INIT;
704 goto on_error;
705 }
706
707 exc = (*jni_env)->ExceptionOccurred(jni_env);
708 if (exc) {
709 (*jni_env)->ExceptionDescribe(jni_env);
710 (*jni_env)->ExceptionClear(jni_env);
711 PJ_LOG(3, (THIS_FILE, "Failure in audio record's constructor"));
712 if (mic_source == 0) {
713 status = PJMEDIA_EAUD_INIT;
714 goto on_error;
715 }
716 mic_source = 0;
717 PJ_LOG(4, (THIS_FILE, "Trying the default audio source."));
718 continue;
719 }
720
721 /* Check state */
722 method_id = (*jni_env)->GetMethodID(jni_env, stream->record_class,
723 "getState", "()I");
724 if (method_id == 0) {
725 PJ_LOG(3, (THIS_FILE, "Unable to find audio record getState() "
726 "method"));
727 status = PJMEDIA_EAUD_SYSERR;
728 goto on_error;
729 }
730 state = (*jni_env)->CallIntMethod(jni_env, stream->record,
731 method_id);
732 if (state == 0) { /* STATE_UNINITIALIZED */
733 PJ_LOG(3, (THIS_FILE, "Failure in initializing audio record."));
734 if (mic_source == 0) {
735 status = PJMEDIA_EAUD_INIT;
736 goto on_error;
737 }
738 mic_source = 0;
739 PJ_LOG(4, (THIS_FILE, "Trying the default audio source."));
740 }
741 } while (state == 0);
742
743 stream->record = (*jni_env)->NewGlobalRef(jni_env, stream->record);
744 if (stream->record == 0) {
745 PJ_LOG(3, (THIS_FILE, "Unable to create audio record global ref."));
746 status = PJMEDIA_EAUD_INIT;
747 goto on_error;
748 }
749
750 status = pj_sem_create(stream->pool, NULL, 0, 1, &stream->rec_sem);
751 if (status != PJ_SUCCESS)
752 goto on_error;
753
754 status = pj_thread_create(stream->pool, "android_recorder",
755 AndroidRecorderCallback, stream, 0, 0,
756 &stream->rec_thread);
757 if (status != PJ_SUCCESS)
758 goto on_error;
759
760 PJ_LOG(4, (THIS_FILE, "Audio record initialized successfully."));
761 }
762
763 if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
764 jthrowable exc;
765
766 /* Get pointer to the constructor */
767 constructor_method = (*jni_env)->GetMethodID(jni_env,
768 stream->track_class,
769 "<init>", "(IIIIII)V");
770 if (constructor_method == 0) {
771 PJ_LOG(3, (THIS_FILE, "Unable to find audio track's constructor."));
772 status = PJMEDIA_EAUD_SYSERR;
773 goto on_error;
774 }
775
776 stream->track = (*jni_env)->NewObject(jni_env,
777 stream->track_class,
778 constructor_method,
779 0, /* STREAM_VOICE_CALL */
780 param->clock_rate,
781 channelOutCfg,
782 sampleFormat,
783 inputBuffSizePlay,
784 1 /* MODE_STREAM */);
785 if (stream->track == 0) {
786 PJ_LOG(3, (THIS_FILE, "Unable to create audio track object."));
787 status = PJMEDIA_EAUD_INIT;
788 goto on_error;
789 }
790
791 exc = (*jni_env)->ExceptionOccurred(jni_env);
792 if (exc) {
793 (*jni_env)->ExceptionDescribe(jni_env);
794 (*jni_env)->ExceptionClear(jni_env);
795 PJ_LOG(3, (THIS_FILE, "Failure in audio track's constructor"));
796 status = PJMEDIA_EAUD_INIT;
797 goto on_error;
798 }
799
800 stream->track = (*jni_env)->NewGlobalRef(jni_env, stream->track);
801 if (stream->track == 0) {
802 PJ_LOG(3, (THIS_FILE, "Unable to create audio track's global ref"));
803 status = PJMEDIA_EAUD_INIT;
804 goto on_error;
805 }
806
807 /* Check state */
808 method_id = (*jni_env)->GetMethodID(jni_env, stream->track_class,
809 "getState", "()I");
810 if (method_id == 0) {
811 PJ_LOG(3, (THIS_FILE, "Unable to find audio track getState() "
812 "method"));
813 status = PJMEDIA_EAUD_SYSERR;
814 goto on_error;
815 }
816 state = (*jni_env)->CallIntMethod(jni_env, stream->track,
817 method_id);
818 if (state == 0) { /* STATE_UNINITIALIZED */
819 PJ_LOG(3, (THIS_FILE, "Failure in initializing audio track."));
820 status = PJMEDIA_EAUD_INIT;
821 goto on_error;
822 }
823
824 status = pj_sem_create(stream->pool, NULL, 0, 1, &stream->play_sem);
825 if (status != PJ_SUCCESS)
826 goto on_error;
827
828 status = pj_thread_create(stream->pool, "android_track",
829 AndroidTrackCallback, stream, 0, 0,
830 &stream->play_thread);
831 if (status != PJ_SUCCESS)
832 goto on_error;
833
834 PJ_LOG(4, (THIS_FILE, "Audio track initialized successfully."));
835 }
836
837 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
838 strm_set_cap(&stream->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
839 &param->output_vol);
840 }
841
842 /* Done */
843 stream->base.op = &android_strm_op;
844 *p_aud_strm = &stream->base;
845
846 detach_jvm(attached);
847
848 return PJ_SUCCESS;
849
850on_error:
851 detach_jvm(attached);
852 strm_destroy(&stream->base);
853 return status;
854}
855
856/* API: Get stream parameters */
857static pj_status_t strm_get_param(pjmedia_aud_stream *s,
858 pjmedia_aud_param *pi)
859{
860 struct android_aud_stream *strm = (struct android_aud_stream*)s;
861 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
862 pj_memcpy(pi, &strm->param, sizeof(*pi));
863
864 if (strm_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
865 &pi->output_vol) == PJ_SUCCESS)
866 {
867 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
868 }
869
870 return PJ_SUCCESS;
871}
872
873/* API: get capability */
874static pj_status_t strm_get_cap(pjmedia_aud_stream *s,
875 pjmedia_aud_dev_cap cap,
876 void *pval)
877{
878 struct android_aud_stream *strm = (struct android_aud_stream*)s;
879 pj_status_t status = PJMEDIA_EAUD_INVCAP;
880
881 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
882
883 if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
884 (strm->param.dir & PJMEDIA_DIR_PLAYBACK))
885 {
886 }
887
888 return status;
889}
890
891/* API: set capability */
892static pj_status_t strm_set_cap(pjmedia_aud_stream *s,
893 pjmedia_aud_dev_cap cap,
894 const void *value)
895{
896 struct android_aud_stream *stream = (struct android_aud_stream*)s;
897 JNIEnv *jni_env = 0;
898 pj_bool_t attached;
899
900 PJ_ASSERT_RETURN(s && value, PJ_EINVAL);
901
902 if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING &&
903 (stream->param.dir & PJMEDIA_DIR_PLAYBACK))
904 {
905 if (stream->track) {
906 jmethodID vol_method = 0;
907 int retval;
908 float vol = *(int *)value;
909
910 attached = attach_jvm(&jni_env);
911
912 vol_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
913 "setStereoVolume", "(FF)I");
914 if (vol_method) {
915 retval = (*jni_env)->CallIntMethod(jni_env, stream->track,
916 vol_method,
917 vol/100, vol/100);
918 }
919
920 detach_jvm(attached);
921
922 if (vol_method && retval == 0)
923 return PJ_SUCCESS;
924 }
925 }
926
927 return PJMEDIA_EAUD_INVCAP;
928}
929
930/* API: start stream. */
931static pj_status_t strm_start(pjmedia_aud_stream *s)
932{
933 struct android_aud_stream *stream = (struct android_aud_stream*)s;
934
935 if (!stream->running) {
936 stream->running = PJ_TRUE;
937 if (stream->record)
938 pj_sem_post(stream->rec_sem);
939 if (stream->track)
940 pj_sem_post(stream->play_sem);
941 }
942
943 PJ_LOG(4, (THIS_FILE, "Android JNI stream started"));
944
945 return PJ_SUCCESS;
946}
947
948/* API: stop stream. */
949static pj_status_t strm_stop(pjmedia_aud_stream *s)
950{
951 struct android_aud_stream *stream = (struct android_aud_stream*)s;
952
953 if (!stream->running)
954 return PJ_SUCCESS;
955
956 stream->running = PJ_FALSE;
957 PJ_LOG(4,(THIS_FILE, "Android JNI stream stopped"));
958
959 return PJ_SUCCESS;
960}
961
962/* API: destroy stream. */
963static pj_status_t strm_destroy(pjmedia_aud_stream *s)
964{
965 struct android_aud_stream *stream = (struct android_aud_stream*)s;
966 JNIEnv *jni_env = 0;
967 jmethodID release_method=0;
968 pj_bool_t attached;
969
970 PJ_LOG(4,(THIS_FILE, "Destroying Android JNI stream..."));
971
972 stream->quit_flag = PJ_TRUE;
973
974 /* Stop the stream */
975 strm_stop(s);
976
977 attached = attach_jvm(&jni_env);
978
979 if (stream->record){
980 if (stream->rec_thread) {
981 pj_sem_post(stream->rec_sem);
982 pj_thread_join(stream->rec_thread);
983 pj_thread_destroy(stream->rec_thread);
984 stream->rec_thread = NULL;
985 }
986
987 if (stream->rec_sem) {
988 pj_sem_destroy(stream->rec_sem);
989 stream->rec_sem = NULL;
990 }
991
992 release_method = (*jni_env)->GetMethodID(jni_env, stream->record_class,
993 "release", "()V");
994 (*jni_env)->CallVoidMethod(jni_env, stream->record, release_method);
995
996 (*jni_env)->DeleteGlobalRef(jni_env, stream->record);
997 (*jni_env)->DeleteGlobalRef(jni_env, stream->record_class);
998 stream->record = NULL;
999 stream->record_class = NULL;
1000 PJ_LOG(4, (THIS_FILE, "Audio record released"));
1001 }
1002
1003 if (stream->track) {
1004 if (stream->play_thread) {
1005 pj_sem_post(stream->play_sem);
1006 pj_thread_join(stream->play_thread);
1007 pj_thread_destroy(stream->play_thread);
1008 stream->play_thread = NULL;
1009 }
1010
1011 if (stream->play_sem) {
1012 pj_sem_destroy(stream->play_sem);
1013 stream->play_sem = NULL;
1014 }
1015
1016 release_method = (*jni_env)->GetMethodID(jni_env, stream->track_class,
1017 "release", "()V");
1018 (*jni_env)->CallVoidMethod(jni_env, stream->track, release_method);
1019
1020 (*jni_env)->DeleteGlobalRef(jni_env, stream->track);
1021 (*jni_env)->DeleteGlobalRef(jni_env, stream->track_class);
1022 stream->track = NULL;
1023 stream->track_class = NULL;
1024 PJ_LOG(3, (THIS_FILE, "Audio track released"));
1025 }
1026
1027 pj_pool_release(stream->pool);
1028 PJ_LOG(4, (THIS_FILE, "Android JNI stream destroyed"));
1029
1030 detach_jvm(attached);
1031 return PJ_SUCCESS;
1032}
1033
1034#endif /* PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI */