androidsound_depr.cpp 29.1 KB
Newer Older
jehan's avatar
jehan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * msandroid.cpp -Android Media plugin for Linphone-
 *
 *
 * Copyright (C) 2009  Belledonne Communications, Grenoble, France
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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
15
 *  GNU General Public License for more details.
jehan's avatar
jehan committed
16 17 18 19 20 21 22 23
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "mediastreamer2/mssndcard.h"
#include "mediastreamer2/msfilter.h"
24
#include "mediastreamer2/msticker.h"
25
#include "mediastreamer2/msjava.h"
26
#include "mediastreamer2/devices.h"
jehan's avatar
jehan committed
27 28
#include <jni.h>

29 30 31
#include <sys/time.h>
#include <sys/resource.h>

32
#include "mediastreamer2/zrtp.h"
33 34
#include <cpu-features.h>

35
#include "hardware_echo_canceller.h"
36

Simon Morlat's avatar
Simon Morlat committed
37 38
static const float sndwrite_flush_threshold=0.020;	//ms
static const float sndread_flush_threshold=0.020; //ms
39
static void sound_read_setup(MSFilter *f);
jehan's avatar
jehan committed
40

41 42 43 44 45 46 47 48 49 50 51 52
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jboolean JNICALL Java_org_linphone_mediastream_Version_nativeHasNeon(JNIEnv *env, jclass c) {
	if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM && (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0)
	{
		return 1;
	}
	return 0;
}

JNIEXPORT jboolean JNICALL Java_org_linphone_mediastream_Version_nativeHasZrtp(JNIEnv *env, jclass c) {
53
	return ms_zrtp_available();
54
}
55 56 57 58 59 60 61 62 63

JNIEXPORT jboolean JNICALL Java_org_linphone_mediastream_Version_nativeHasVideo(JNIEnv *env, jclass c) {
#ifdef VIDEO_ENABLED
	return JNI_TRUE;
#else
	return JNI_FALSE;
#endif
}

64 65
#ifdef __cplusplus
}
66
#endif
67

68
static void set_high_prio(void){
69
	/*
70
		This pthread based code does nothing on linux. The linux kernel has
71 72 73 74 75
		sched_get_priority_max(SCHED_OTHER)=sched_get_priority_max(SCHED_OTHER)=0.
		As long as we can't use SCHED_RR or SCHED_FIFO, the only way to increase priority of a calling thread
		is to use setpriority().
	*/
#if 0
76 77 78 79 80 81 82 83
	struct sched_param param;
	int result=0;
	memset(&param,0,sizeof(param));
	int policy=SCHED_OTHER;
	param.sched_priority=sched_get_priority_max(policy);
	if((result=pthread_setschedparam(pthread_self(),policy, &param))) {
		ms_warning("Set sched param failed with error code(%i)\n",result);
	} else {
84 85 86 87 88
		ms_message("msandroid thread priority set to max (%i, min=%i)",sched_get_priority_max(policy),sched_get_priority_min(policy));
	}
#endif
	if (setpriority(PRIO_PROCESS,0,-20)==-1){
		ms_warning("msandroid set_high_prio() failed: %s",strerror(errno));
89 90
	}
}
jehan's avatar
jehan committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
/*
 mediastreamer2 sound card functions
 */

void msandroid_sound_set_level(MSSndCard *card, MSSndCardMixerElem e, int percent)
{
}

int msandroid_sound_get_level(MSSndCard *card, MSSndCardMixerElem e)
{
	return 0;
}

void msandroid_sound_set_source(MSSndCard *card, MSSndCardCapture source)
{
}

108 109
static int sdk_version=0;

jehan's avatar
jehan committed
110
void msandroid_sound_init(MSSndCard *card){
111 112 113 114 115 116
	/*get running sdk version*/
	JNIEnv *jni_env = ms_get_jni_env();
	jclass version_class = jni_env->FindClass("android/os/Build$VERSION");
	jfieldID fid = jni_env->GetStaticFieldID(version_class, "SDK_INT", "I");
	sdk_version=jni_env->GetStaticIntField(version_class, fid);
	ms_message("SDK version [%i] detected",sdk_version);
jehan's avatar
jehan committed
117
	jni_env->DeleteLocalRef(version_class);
jehan's avatar
jehan committed
118 119 120 121 122 123 124 125 126 127 128 129
}

void msandroid_sound_uninit(MSSndCard *card){
}

void msandroid_sound_detect(MSSndCardManager *m);
MSSndCard *msandroid_sound_duplicate(MSSndCard *obj);

MSFilter *msandroid_sound_read_new(MSSndCard *card);
MSFilter *msandroid_sound_write_new(MSSndCard *card);

MSSndCardDesc msandroid_sound_card_desc = {
130
/*.driver_type=*/"ANDROID SND (deprecated)",
jehan's avatar
jehan committed
131 132 133 134 135 136 137 138 139 140 141 142 143 144
/*.detect=*/ msandroid_sound_detect,
/*.init=*/msandroid_sound_init,
/*.set_level=*/msandroid_sound_set_level,
/*.get_level=*/msandroid_sound_get_level,
/*.set_capture=*/msandroid_sound_set_source,
/*.set_control=*/NULL,
/*.get_control=*/NULL,
/*.create_reader=*/msandroid_sound_read_new,
/*.create_writer=*/msandroid_sound_write_new,
/*.uninit=*/msandroid_sound_uninit,
/*.duplicate=*/msandroid_sound_duplicate
};

MSSndCard *msandroid_sound_duplicate(MSSndCard *obj){
Guillaume Beraudo's avatar
Guillaume Beraudo committed
145
	MSSndCard *card=ms_snd_card_new(&msandroid_sound_card_desc);
jehan's avatar
jehan committed
146 147 148 149 150
	card->name=ms_strdup(obj->name);
	return card;
}

MSSndCard *msandroid_sound_card_new(){
151
	SoundDeviceDescription *d;
jehan's avatar
jehan committed
152 153
	MSSndCard *card=ms_snd_card_new(&msandroid_sound_card_desc);
	card->name=ms_strdup("Android Sound card");
154 155 156 157 158

	d = sound_device_description_get();
	if (d->flags & DEVICE_HAS_BUILTIN_AEC) {
		card->capabilities |= MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER;
	}
159
	card->data = d;
jehan's avatar
jehan committed
160 161 162 163 164 165 166 167 168 169 170 171 172
	return card;
}

void msandroid_sound_detect(MSSndCardManager *m){
	ms_debug("msandroid_sound_detect");
	MSSndCard *card=msandroid_sound_card_new();
	ms_snd_card_manager_add_card(m,card);
}


/*************filter commun functions*********/
class msandroid_sound_data {
public:
173
	msandroid_sound_data() : bits(16),rate(8000),nchannels(1),started(false),thread_id(0),forced_rate(false){
jehan's avatar
jehan committed
174 175 176 177 178 179
		ms_mutex_init(&mutex,NULL);
	};
	~msandroid_sound_data() {
		ms_mutex_destroy(&mutex);
	}
	unsigned int	bits;
Guillaume Beraudo's avatar
Guillaume Beraudo committed
180
	unsigned int	rate;
jehan's avatar
jehan committed
181 182 183
	unsigned int	nchannels;
	bool			started;
	ms_thread_t     thread_id;
Guillaume Beraudo's avatar
Guillaume Beraudo committed
184
	ms_mutex_t		mutex;
jehan's avatar
jehan committed
185
	int	buff_size; /*buffer size in bytes*/
186
	bool	forced_rate;
jehan's avatar
jehan committed
187 188 189
};


190
static int get_rate(MSFilter *f, void *data){
jehan's avatar
jehan committed
191 192 193 194 195 196
	msandroid_sound_data *d=(msandroid_sound_data*)f->data;
	*(int*)data=d->rate;
	return 0;
}


197
static int set_nchannels(MSFilter *f, void *arg){
jehan's avatar
jehan committed
198 199 200 201 202 203
	ms_debug("set_nchannels %d", *((int*)arg));
	msandroid_sound_data *d=(msandroid_sound_data*)f->data;
	d->nchannels=*(int*)arg;
	return 0;
}

Simon Morlat's avatar
Simon Morlat committed
204 205 206 207 208 209 210
static int get_nchannels(MSFilter *f, void *arg){
	ms_debug("get_nchannels %d", *((int*)arg));
	msandroid_sound_data *d=(msandroid_sound_data*)f->data;
	*(int*)arg = d->nchannels;
	return 0;
}

jehan's avatar
jehan committed
211

jehan's avatar
jehan committed
212
static unsigned int get_supported_rate(unsigned int prefered_rate) {
213 214 215 216 217 218 219
	JNIEnv *jni_env = ms_get_jni_env();
	jclass audio_record_class = jni_env->FindClass("android/media/AudioRecord");
	int size = jni_env->CallStaticIntMethod(audio_record_class
											,jni_env->GetStaticMethodID(audio_record_class,"getMinBufferSize", "(III)I")
											,prefered_rate
											,2/*CHANNEL_CONFIGURATION_MONO*/
											,2/*  ENCODING_PCM_16BIT */);
jehan's avatar
jehan committed
220 221


222 223 224 225 226 227 228 229 230 231 232
	if (size > 0) {
		return prefered_rate;
	} else {
		ms_warning("Cannot configure recorder with rate [%i]",prefered_rate);
		if (prefered_rate>48000) {
			return get_supported_rate(48000);
		}
		switch (prefered_rate) {
		case 12000:
		case 24000: return get_supported_rate(48000);
		case 48000: return get_supported_rate(44100);
Simon Morlat's avatar
Simon Morlat committed
233
		case 44100: return get_supported_rate(16000);
234 235 236
		case 16000: return get_supported_rate(8000);
				default: {
					ms_error("This Android sound card doesn't support any standard sample rate");
jehan's avatar
jehan committed
237
					return 0;
238 239 240
				}
		}

jehan's avatar
jehan committed
241
		return 0;
242 243 244 245
	}

}

jehan's avatar
jehan committed
246
/***********************************read filter********************/
247
static int set_read_rate(MSFilter *f, void *arg){
jehan's avatar
jehan committed
248
	unsigned int proposed_rate = *((unsigned int*)arg);
jehan's avatar
jehan committed
249 250
	ms_debug("set_rate %d",proposed_rate);
	msandroid_sound_data *d=(msandroid_sound_data*)f->data;
251 252 253 254
	if (d->forced_rate) {
		ms_warning("sample rate is forced by mediastreamer2 device table, skipping...");
		return -1;
	}
255

256 257 258 259 260
	d->rate=get_supported_rate(proposed_rate);
	if (d->rate == proposed_rate)
		return 0;
	else
		return -1;
261 262 263 264 265 266
}

static int get_latency(MSFilter *f, void *arg){
	msandroid_sound_data *d=(msandroid_sound_data*)f->data;
	if (!d->started){
		sound_read_setup(f);
Simon Morlat's avatar
Simon Morlat committed
267
		*((int*)arg)=(1000*d->buff_size)/(d->nchannels*2*d->rate);
268
	}
jehan's avatar
jehan committed
269 270
	return 0;
}
271 272

static int msandroid_hack_speaker_state(MSFilter *f, void *arg);
jehan's avatar
jehan committed
273 274 275 276 277

MSFilterMethod msandroid_sound_read_methods[]={
	{	MS_FILTER_SET_SAMPLE_RATE	, set_read_rate	},
	{	MS_FILTER_GET_SAMPLE_RATE	, get_rate	},
	{	MS_FILTER_SET_NCHANNELS		, set_nchannels	},
Simon Morlat's avatar
Simon Morlat committed
278
	{	MS_FILTER_GET_NCHANNELS		, get_nchannels	},
279
	{	MS_FILTER_GET_LATENCY	, get_latency},
280
	{	MS_AUDIO_CAPTURE_FORCE_SPEAKER_STATE,	msandroid_hack_speaker_state	},
jehan's avatar
jehan committed
281 282 283 284 285 286
	{	0				, NULL		}
};


class msandroid_sound_read_data : public msandroid_sound_data{
public:
287 288
	msandroid_sound_read_data() : audio_record(0),audio_record_class(0),read_buff(0),read_chunk_size(0) {
		ms_bufferizer_init(&rb);
289
		aec=NULL;
290 291 292
	}
	~msandroid_sound_read_data() {
		ms_bufferizer_uninit (&rb);
jehan's avatar
jehan committed
293 294 295 296
	}
	jobject			audio_record;
	jclass 			audio_record_class;
	jbyteArray		read_buff;
297 298
	MSBufferizer 		rb;
	int			read_chunk_size;
299 300 301 302 303
	int framesize;
	int outgran_ms;
	int min_avail;
	int64_t start_time;
	int64_t read_samples;
304
	MSTickerSynchronizer *ticker_synchronizer;
305 306
	jobject aec;
	bool builtin_aec;
jehan's avatar
jehan committed
307 308
};

Yann Diorcet's avatar
Yann Diorcet committed
309 310 311
static void compute_timespec(msandroid_sound_read_data *d) {
	static int count = 0;
	uint64_t ns = ((1000 * d->read_samples) / (uint64_t) d->rate) * 1000000;
Simon Morlat's avatar
Simon Morlat committed
312
	MSTimeSpec ts;
Yann Diorcet's avatar
Yann Diorcet committed
313 314 315 316 317
	ts.tv_nsec = ns % 1000000000;
	ts.tv_sec = ns / 1000000000;
	double av_skew = ms_ticker_synchronizer_set_external_time(d->ticker_synchronizer, &ts);
	if ((++count) % 100 == 0)
		ms_message("sound/wall clock skew is average=%f ms", av_skew);
Simon Morlat's avatar
Simon Morlat committed
318 319
}

320
static void* msandroid_read_cb(msandroid_sound_read_data* d) {
jehan's avatar
jehan committed
321 322 323 324 325
	mblk_t *m;
	int nread;
	jmethodID read_id=0;
	jmethodID record_id=0;

326
	set_high_prio();
jehan's avatar
jehan committed
327

328
	JNIEnv *jni_env = ms_get_jni_env();
jehan's avatar
jehan committed
329 330 331 332 333 334
	record_id = jni_env->GetMethodID(d->audio_record_class,"startRecording", "()V");
	if(record_id==0) {
		ms_error("cannot find AudioRecord.startRecording() method");
		goto end;
	}
	//start recording
335
	ms_message("Start recording");
jehan's avatar
jehan committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349
	jni_env->CallVoidMethod(d->audio_record,record_id);

	// int read (byte[] audioData, int offsetInBytes, int sizeInBytes)
	read_id = jni_env->GetMethodID(d->audio_record_class,"read", "([BII)I");
	if(read_id==0) {
		ms_error("cannot find AudioRecord.read() method");
		goto end;
	}

	while (d->started && (nread=jni_env->CallIntMethod(d->audio_record,read_id,d->read_buff,0, d->read_chunk_size))>0) {
		m = allocb(nread,0);
		jni_env->GetByteArrayRegion(d->read_buff, 0,nread, (jbyte*)m->b_wptr);
		//ms_error("%i octets read",nread);
		m->b_wptr += nread;
350
		d->read_samples+=nread/(2*d->nchannels);
351
		compute_timespec(d);
jehan's avatar
jehan committed
352
		ms_mutex_lock(&d->mutex);
353
		ms_bufferizer_put (&d->rb,m);
jehan's avatar
jehan committed
354 355
		ms_mutex_unlock(&d->mutex);
	};
356

jehan's avatar
jehan committed
357 358
	goto end;
	end: {
359 360
		ms_thread_exit(NULL);
		return 0;
jehan's avatar
jehan committed
361 362 363
	}
}

364
static void sound_read_setup(MSFilter *f){
jehan's avatar
jehan committed
365 366
	ms_debug("andsnd_read_preprocess");
	msandroid_sound_read_data *d=(msandroid_sound_read_data*)f->data;
367 368
	jmethodID constructor_id=0, methodID = 0;
	int audio_record_state=0;
jehan's avatar
jehan committed
369 370 371 372
	jmethodID min_buff_size_id;
	//jmethodID set_notification_period;
	int rc;

373
	JNIEnv *jni_env = ms_get_jni_env();
jehan's avatar
jehan committed
374 375
	d->audio_record_class = (jclass)jni_env->NewGlobalRef(jni_env->FindClass("android/media/AudioRecord"));
	if (d->audio_record_class == 0) {
376
		ms_error("cannot find android/media/AudioRecord");
377
		return;
jehan's avatar
jehan committed
378 379 380 381
	}

	constructor_id = jni_env->GetMethodID(d->audio_record_class,"<init>", "(IIIII)V");
	if (constructor_id == 0) {
382
		ms_error("cannot find AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)");
383
		return;
jehan's avatar
jehan committed
384 385 386
	}
	min_buff_size_id = jni_env->GetStaticMethodID(d->audio_record_class,"getMinBufferSize", "(III)I");
	if (min_buff_size_id == 0) {
387
		ms_error("cannot find AudioRecord.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)");
388
		return;
jehan's avatar
jehan committed
389 390
	}
	d->buff_size = jni_env->CallStaticIntMethod(d->audio_record_class,min_buff_size_id,d->rate,2/*CHANNEL_CONFIGURATION_MONO*/,2/*  ENCODING_PCM_16BIT */);
391
	d->read_chunk_size = d->buff_size/4;
392
	d->buff_size*=2;/*double the size for configuring the recorder: this does not affect latency but prevents "AudioRecordThread: buffer overflow"*/
jehan's avatar
jehan committed
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

	if (d->buff_size > 0) {
		ms_message("Configuring recorder with [%i] bits  rate [%i] nchanels [%i] buff size [%i], chunk size [%i]"
				,d->bits
				,d->rate
				,d->nchannels
				,d->buff_size
				,d->read_chunk_size);
	} else {
		ms_message("Cannot configure recorder with [%i] bits  rate [%i] nchanels [%i] buff size [%i] chunk size [%i]"
				,d->bits
				,d->rate
				,d->nchannels
				,d->buff_size
				,d->read_chunk_size);
408
		return;
jehan's avatar
jehan committed
409 410 411 412 413 414
	}

	d->read_buff = jni_env->NewByteArray(d->buff_size);
	d->read_buff = (jbyteArray)jni_env->NewGlobalRef(d->read_buff);
	if (d->read_buff == 0) {
		ms_error("cannot instanciate read buff");
415
		return;
jehan's avatar
jehan committed
416 417 418 419
	}

	d->audio_record =  jni_env->NewObject(d->audio_record_class
			,constructor_id
420
			,sdk_version<11?1/*MIC*/:7/*VOICE_COMMUNICATION*/
jehan's avatar
jehan committed
421 422 423 424 425
			,d->rate
			,2/*CHANNEL_CONFIGURATION_MONO*/
			,2/*  ENCODING_PCM_16BIT */
			,d->buff_size);

426 427 428 429
	//Check the state of the AudioRecord (uninitialized = 1
	methodID = jni_env->GetMethodID(d->audio_record_class,"getState", "()I");
	if (methodID == 0) {
		ms_error("cannot find AudioRecord getState() method");
430
		return;
jehan's avatar
jehan committed
431
	}
432 433 434 435 436 437 438 439 440 441 442 443 444
	audio_record_state = jni_env->CallIntMethod(d->audio_record, methodID);

	if(audio_record_state == 1) {
		d->audio_record = jni_env->NewGlobalRef(d->audio_record);
		if (d->audio_record == 0) {
			ms_error("cannot instantiate AudioRecord");
			return;
		}
	} else {
		d->audio_record = NULL;
		ms_error("AudioRecord is not initialized properly. It may be caused by RECORD_AUDIO permission not granted");
	}

445 446
	d->min_avail=-1;
	d->read_samples=0;
447
	d->ticker_synchronizer = ms_ticker_synchronizer_new();
448 449 450
	d->outgran_ms=20;
	d->start_time=-1;
	d->framesize=(d->outgran_ms*d->rate)/1000;
jehan's avatar
jehan committed
451 452
	d->started=true;
	// start reader thread
453 454 455 456 457 458
	if(d->audio_record) {
		rc = ms_thread_create(&d->thread_id, 0, (void*(*)(void*))msandroid_read_cb, d);
		if (rc){
			ms_error("cannot create read thread return code  is [%i]", rc);
			d->started=false;
        }
jehan's avatar
jehan committed
459 460 461
	}
}

462
static void sound_read_preprocess(MSFilter *f){
Simon Morlat's avatar
Simon Morlat committed
463
	msandroid_sound_read_data *d=(msandroid_sound_read_data*)f->data;
464 465 466
	ms_debug("andsnd_read_preprocess");
	if (!d->started)
		sound_read_setup(f);
Yann Diorcet's avatar
Yann Diorcet committed
467
	ms_ticker_set_time_func(f->ticker,(uint64_t (*)(void*))ms_ticker_synchronizer_get_corrected_time, d->ticker_synchronizer);
468

469 470 471 472 473 474 475 476 477 478
	if (d->builtin_aec && d->audio_record) {
		JNIEnv *env=ms_get_jni_env();
		jmethodID getsession_id=0;
		int sessionId=-1;
		getsession_id = env->GetMethodID(d->audio_record_class,"getAudioSessionId", "()I");
		if(getsession_id==0) {
			ms_error("cannot find AudioRecord.getAudioSessionId() method");
			return;
		}
		sessionId = env->CallIntMethod(d->audio_record,getsession_id);
479 480 481 482
		ms_message("AudioRecord.getAudioSessionId() returned %i", sessionId);
		if (sessionId==-1) {
			return;
		}
483 484
		d->aec = enable_hardware_echo_canceller(env, sessionId);
	}
485 486 487
}

static void sound_read_postprocess(MSFilter *f){
jehan's avatar
jehan committed
488 489 490
	msandroid_sound_read_data *d=(msandroid_sound_read_data*)f->data;
	jmethodID stop_id=0;
	jmethodID release_id=0;
491 492

	JNIEnv *jni_env = ms_get_jni_env();
jehan's avatar
jehan committed
493

494
	ms_ticker_set_time_func(f->ticker,NULL,NULL);
495
	d->read_samples=0;
496

jehan's avatar
jehan committed
497 498 499 500 501 502
	//stop recording
	stop_id = jni_env->GetMethodID(d->audio_record_class,"stop", "()V");
	if(stop_id==0) {
		ms_error("cannot find AudioRecord.stop() method");
		goto end;
	}
503

jehan's avatar
jehan committed
504
	d->started = false;
505 506 507 508
	if (d->thread_id !=0 ){
		ms_thread_join(d->thread_id,0);
		d->thread_id = 0;
	}
jehan's avatar
jehan committed
509 510 511 512 513 514 515 516 517 518 519 520

	if (d->audio_record) {
		jni_env->CallVoidMethod(d->audio_record,stop_id);

		//release recorder
		release_id = jni_env->GetMethodID(d->audio_record_class,"release", "()V");
		if(release_id==0) {
			ms_error("cannot find AudioRecord.release() method");
			goto end;
		}
		jni_env->CallVoidMethod(d->audio_record,release_id);
	}
521 522 523 524
	if (d->aec) {
		delete_hardware_echo_canceller(jni_env, d->aec);
		d->aec = NULL;
	}
jehan's avatar
jehan committed
525 526 527 528 529 530 531 532 533
	goto end;
	end: {
		if (d->audio_record) jni_env->DeleteGlobalRef(d->audio_record);
		jni_env->DeleteGlobalRef(d->audio_record_class);
		if (d->read_buff) jni_env->DeleteGlobalRef(d->read_buff);
		return;
	}
}

534
static void sound_read_process(MSFilter *f){
jehan's avatar
jehan committed
535
	msandroid_sound_read_data *d=(msandroid_sound_read_data*)f->data;
536 537 538 539
	int nbytes=d->framesize*d->nchannels*2;
	int avail;
	bool_t flush=FALSE;
	bool_t can_output=(d->start_time==-1 || ((f->ticker->time-d->start_time)%d->outgran_ms==0));
jehan's avatar
jehan committed
540

541 542
	ms_mutex_lock(&d->mutex);
	if (!d->started) {
543
		ms_mutex_unlock(&d->mutex);
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
		return;
	}
	avail=ms_bufferizer_get_avail(&d->rb);
	if (f->ticker->time % 5000==0){
		if (d->min_avail>=(sndread_flush_threshold*(float)d->rate*2.0*(float)d->nchannels)){
			int excess_ms=(d->min_avail*1000)/(d->rate*2*d->nchannels);
			ms_warning("Excess of audio samples in capture side bytes=%i (%i ms)",d->min_avail,excess_ms);
			can_output=TRUE;
			flush=TRUE;
		}
		d->min_avail=-1;
	}
	do{
		if (can_output && (avail>=nbytes*2)){//bytes*2 is to insure smooth output, we leave at least one packet in the buffer for next time*/
			mblk_t *om=allocb(nbytes,0);
			ms_bufferizer_read(&d->rb,om->b_wptr,nbytes);
560 561
			om->b_wptr+=nbytes;
			ms_queue_put(f->outputs[0],om);
562 563 564 565 566 567 568
			//ms_message("Out time=%llu ",f->ticker->time);
			if (d->start_time==-1) d->start_time=f->ticker->time;
			avail-=nbytes;
		}else break;
	}while(flush);
	ms_mutex_unlock(&d->mutex);
	if (d->min_avail==-1 || avail<d->min_avail) d->min_avail=avail;
jehan's avatar
jehan committed
569 570 571 572 573 574 575 576 577 578 579 580
}


static MSFilterDesc msandroid_sound_read_desc={
/*.id=*/MS_FILTER_PLUGIN_ID,
/*.name=*/"MSAndSoundRead",
/*.text=*/N_("Sound capture filter for Android"),
/*.category=*/MS_FILTER_OTHER,
/*.enc_fmt*/NULL,
/*.ninputs=*/0,
/*.noutputs=*/1,
/*.init*/NULL,
581 582 583
/*.preprocess=*/sound_read_preprocess,
/*.process=*/sound_read_process,
/*.postprocess=*/sound_read_postprocess,
jehan's avatar
jehan committed
584 585 586 587 588 589
/*.uninit*/NULL,
/*.methods=*/msandroid_sound_read_methods
};

MSFilter *msandroid_sound_read_new(MSSndCard *card){
	ms_debug("msandroid_sound_read_new");
590
	MSFilter *f=ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &msandroid_sound_read_desc);
591 592
	msandroid_sound_read_data *data=new msandroid_sound_read_data();
	data->builtin_aec = card->capabilities & MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER;
593 594 595 596 597 598 599
	if (card->data != NULL) {
		SoundDeviceDescription *d = (SoundDeviceDescription *)card->data;
		if (d->recommended_rate > 0) {
			data->rate = d->recommended_rate;
			data->forced_rate = true;
			ms_warning("Using forced sample rate %i", data->rate);
		}
600
	}
601
	f->data=data;
jehan's avatar
jehan committed
602 603 604 605
	return f;
}

/***********************************write filter********************/
606
static int set_write_rate(MSFilter *f, void *arg){
Simon Morlat's avatar
Simon Morlat committed
607
#ifndef USE_HARDWARE_RATE
Guillaume Beraudo's avatar
Guillaume Beraudo committed
608
	msandroid_sound_data *d=(msandroid_sound_data*)f->data;
609 610 611 612
	if (d->forced_rate) {
		ms_warning("sample rate is forced by mediastreamer2 device table, skipping...");
		return -1;
	}
613

jehan's avatar
jehan committed
614 615 616 617
	int proposed_rate = *((int*)arg);
	ms_debug("set_rate %d",proposed_rate);
	d->rate=proposed_rate;
	return 0;
Simon Morlat's avatar
Simon Morlat committed
618 619 620 621 622
#else
/*audioflingler resampling is really bad
we prefer do resampling by ourselves if cpu allows it*/
	return -1;
#endif
jehan's avatar
jehan committed
623 624 625 626 627 628
}

MSFilterMethod msandroid_sound_write_methods[]={
	{	MS_FILTER_SET_SAMPLE_RATE	, set_write_rate	},
	{	MS_FILTER_GET_SAMPLE_RATE	, get_rate	},
	{	MS_FILTER_SET_NCHANNELS		, set_nchannels	},
Simon Morlat's avatar
Simon Morlat committed
629
	{	MS_FILTER_GET_NCHANNELS		, get_nchannels	},
jehan's avatar
jehan committed
630 631 632 633 634 635
	{	0				, NULL		}
};


class msandroid_sound_write_data : public msandroid_sound_data{
public:
636
	msandroid_sound_write_data() :audio_track_class(0),audio_track(0),write_chunk_size(0),writtenBytes(0),last_sample_date(0){
jehan's avatar
jehan committed
637 638
		bufferizer = ms_bufferizer_new();
		ms_cond_init(&cond,0);
639
		JNIEnv *jni_env = ms_get_jni_env();
Simon Morlat's avatar
Simon Morlat committed
640 641 642 643 644 645 646 647 648 649 650 651
		audio_track_class = (jclass)jni_env->NewGlobalRef(jni_env->FindClass("android/media/AudioTrack"));
		if (audio_track_class == 0) {
			ms_error("cannot find  android/media/AudioTrack\n");
			return;
		}
		jmethodID hwrate_id = jni_env->GetStaticMethodID(audio_track_class,"getNativeOutputSampleRate", "(I)I");
		if (hwrate_id == 0) {
			ms_error("cannot find  int AudioRecord.getNativeOutputSampleRate(int streamType)");
			return;
		}
		rate = jni_env->CallStaticIntMethod(audio_track_class,hwrate_id,0 /*STREAM_VOICE_CALL*/);
		ms_message("Hardware sample rate is %i",rate);
jehan's avatar
jehan committed
652 653 654 655 656
	};
	~msandroid_sound_write_data() {
		ms_bufferizer_flush(bufferizer);
		ms_bufferizer_destroy(bufferizer);
		ms_cond_destroy(&cond);
Simon Morlat's avatar
Simon Morlat committed
657
		if (audio_track_class!=0){
658 659
			JNIEnv *env = ms_get_jni_env();
			env->DeleteGlobalRef(audio_track_class);
Simon Morlat's avatar
Simon Morlat committed
660
		}
jehan's avatar
jehan committed
661 662 663 664 665 666 667
	}
	jclass 			audio_track_class;
	jobject			audio_track;
	MSBufferizer	*bufferizer;
	ms_cond_t		cond;
	int 			write_chunk_size;
	unsigned int	writtenBytes;
668 669
	unsigned long 	last_sample_date;
	bool sleeping;
jehan's avatar
jehan committed
670 671 672 673 674 675 676 677
	unsigned int getWriteBuffSize() {
		return buff_size;
	}
	int getWrittenFrames() {
		return writtenBytes/(nchannels*(bits/8));
	}
};

678
static void* msandroid_write_cb(msandroid_sound_write_data* d) {
jehan's avatar
jehan committed
679 680 681
	jbyteArray 		write_buff;
	jmethodID 		write_id=0;
	jmethodID play_id=0;
Simon Morlat's avatar
Simon Morlat committed
682 683 684 685 686
	int min_size=-1;
	int count;
	int max_size=sndwrite_flush_threshold*(float)d->rate*(float)d->nchannels*2.0;
	int check_point_size=3*(float)d->rate*(float)d->nchannels*2.0; /*3 seconds*/
	int nwrites=0;
jehan's avatar
jehan committed
687

688
	int buff_size = d->write_chunk_size;
689
	uint8_t tmpBuff[buff_size];
690
	JNIEnv *jni_env = ms_get_jni_env();
jehan's avatar
jehan committed
691

692 693
	set_high_prio();

jehan's avatar
jehan committed
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
	// int write  (byte[] audioData, int offsetInBytes, int sizeInBytes)
	write_id = jni_env->GetMethodID(d->audio_track_class,"write", "([BII)I");
	if(write_id==0) {
		ms_error("cannot find AudioTrack.write() method");
		goto end;
	}
	play_id = jni_env->GetMethodID(d->audio_track_class,"play", "()V");
	if(play_id==0) {
		ms_error("cannot find AudioTrack.play() method");
		goto end;
	}
	write_buff = jni_env->NewByteArray(buff_size);

	//start playing
	jni_env->CallVoidMethod(d->audio_track,play_id);

710
	ms_mutex_lock(&d->mutex);
jehan's avatar
jehan committed
711
	ms_bufferizer_flush(d->bufferizer);
712 713
	ms_mutex_unlock(&d->mutex);

jehan's avatar
jehan committed
714 715
	while(d->started) {
		int bufferizer_size;
716 717

		ms_mutex_lock(&d->mutex);
Simon Morlat's avatar
Simon Morlat committed
718 719
		min_size=-1;
		count=0;
jehan's avatar
jehan committed
720
		while((bufferizer_size = ms_bufferizer_get_avail(d->bufferizer)) >= d->write_chunk_size) {
Simon Morlat's avatar
Simon Morlat committed
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
			if (min_size==-1) min_size=bufferizer_size;
			else if (bufferizer_size<min_size) min_size=bufferizer_size;

			ms_bufferizer_read(d->bufferizer, tmpBuff, d->write_chunk_size);
			ms_mutex_unlock(&d->mutex);
			jni_env->SetByteArrayRegion(write_buff,0,d->write_chunk_size,(jbyte*)tmpBuff);
			int result = jni_env->CallIntMethod(d->audio_track,write_id,write_buff,0,d->write_chunk_size);
			d->writtenBytes+=result;
			if (result <= 0) {
				ms_error("write operation has failed [%i]",result);
			}
			nwrites++;
			ms_mutex_lock(&d->mutex);
			count+=d->write_chunk_size;
			if (count>check_point_size){
				if (min_size > max_size) {
					ms_warning("we are late, flushing %i bytes",min_size);
					ms_bufferizer_skip_bytes(d->bufferizer,min_size);
jehan's avatar
jehan committed
739
				}
Simon Morlat's avatar
Simon Morlat committed
740
				count=0;
jehan's avatar
jehan committed
741 742
			}
		}
743 744 745 746 747
		if (d->started) {
			d->sleeping=true;
			ms_cond_wait(&d->cond,&d->mutex);
			d->sleeping=false;
		}
jehan's avatar
jehan committed
748 749 750
		ms_mutex_unlock(&d->mutex);
	}

751

jehan's avatar
jehan committed
752 753
	goto end;
	end: {
754
		ms_thread_exit(NULL);
755
		return NULL;
jehan's avatar
jehan committed
756 757
	}
}
758

jehan's avatar
jehan committed
759 760 761 762 763 764 765 766
void msandroid_sound_write_preprocess(MSFilter *f){
	ms_debug("andsnd_write_preprocess");
	msandroid_sound_write_data *d=(msandroid_sound_write_data*)f->data;
	jmethodID constructor_id=0;

	int rc;
	jmethodID min_buff_size_id;

767
	JNIEnv *jni_env = ms_get_jni_env();
768

jehan's avatar
jehan committed
769
	if (d->audio_track_class == 0) {
770
		return;
jehan's avatar
jehan committed
771 772 773 774 775 776
	}

	constructor_id = jni_env->GetMethodID(d->audio_track_class,"<init>", "(IIIIII)V");
	if (constructor_id == 0) {
		ms_error("cannot find  AudioTrack(int streamType, int sampleRateInHz, \
		int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)");
777
		return;
jehan's avatar
jehan committed
778 779 780 781 782
	}

	min_buff_size_id = jni_env->GetStaticMethodID(d->audio_track_class,"getMinBufferSize", "(III)I");
	if (min_buff_size_id == 0) {
		ms_error("cannot find  AudioTrack.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)");
783
		return;
jehan's avatar
jehan committed
784 785 786
	}
	d->buff_size = jni_env->CallStaticIntMethod(d->audio_track_class,min_buff_size_id,d->rate,2/*CHANNEL_CONFIGURATION_MONO*/,2/*  ENCODING_PCM_16BIT */);
	d->write_chunk_size= (d->rate*(d->bits/8)*d->nchannels)*0.02;
787
	//d->write_chunk_size=d->buff_size;
jehan's avatar
jehan committed
788 789 790 791 792 793 794 795 796 797 798 799 800 801
	if (d->buff_size > 0) {
		ms_message("Configuring player with [%i] bits  rate [%i] nchanels [%i] buff size [%i] chunk size [%i]"
				,d->bits
				,d->rate
				,d->nchannels
				,d->buff_size
				,d->write_chunk_size);
	} else {
		ms_message("Cannot configure player with [%i] bits  rate [%i] nchanels [%i] buff size [%i] chunk size [%i]"
				,d->bits
				,d->rate
				,d->nchannels
				,d->buff_size
				,d->write_chunk_size);
802
		return;
jehan's avatar
jehan committed
803 804 805 806 807 808 809 810 811 812 813 814
	}
	d->audio_track =  jni_env->NewObject(d->audio_track_class
			,constructor_id
			,0/*STREAM_VOICE_CALL*/
			,d->rate
			,2/*CHANNEL_CONFIGURATION_MONO*/
			,2/*  ENCODING_PCM_16BIT */
			,d->buff_size
			,1/*MODE_STREAM */);
	d->audio_track = jni_env->NewGlobalRef(d->audio_track);
	if (d->audio_track == 0) {
		ms_error("cannot instanciate AudioTrack");
815
		return;
jehan's avatar
jehan committed
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
	}


	// start reader thread
	d->started = true;
	rc = ms_thread_create(&d->thread_id, 0, (void*(*)(void*))msandroid_write_cb, d);
	if (rc){
		ms_error("cannot create write thread return code  is [%i]", rc);
		d->started = false;
		return;
	}
}

void msandroid_sound_write_postprocess(MSFilter *f){
	msandroid_sound_write_data *d=(msandroid_sound_write_data*)f->data;
	jmethodID flush_id=0;
	jmethodID stop_id=0;
	jmethodID release_id=0;
834
	JNIEnv *jni_env = ms_get_jni_env();
jehan's avatar
jehan committed
835 836 837 838 839

	d->started=false;
	ms_mutex_lock(&d->mutex);
	ms_cond_signal(&d->cond);
	ms_mutex_unlock(&d->mutex);
840 841 842 843
	if (d->thread_id != 0){
		ms_thread_join(d->thread_id, NULL);
		d->thread_id = 0;
	}
jehan's avatar
jehan committed
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
	// flush
	flush_id = jni_env->GetMethodID(d->audio_track_class,"flush", "()V");
	if(flush_id==0) {
		ms_error("cannot find AudioTrack.flush() method");
		goto end;
	}
	if (d->audio_track) {

		jni_env->CallVoidMethod(d->audio_track,flush_id);

		//stop playing
		stop_id = jni_env->GetMethodID(d->audio_track_class,"stop", "()V");
		if(stop_id==0) {
			ms_error("cannot find AudioTrack.stop() method");
			goto end;
		}
		jni_env->CallVoidMethod(d->audio_track,stop_id);

		//release playing
		release_id = jni_env->GetMethodID(d->audio_track_class,"release", "()V");
		if(release_id==0) {
			ms_error("cannot find AudioTrack.release() method");
			goto end;
		}
		jni_env->CallVoidMethod(d->audio_track,release_id);
	}

	goto end;
end: {
	if (d->audio_track) jni_env->DeleteGlobalRef(d->audio_track);
	//d->jvm->DetachCurrentThread();
	return;
}

}



void msandroid_sound_write_process(MSFilter *f){
	msandroid_sound_write_data *d=(msandroid_sound_write_data*)f->data;
884

jehan's avatar
jehan committed
885 886
	mblk_t *m;
	while((m=ms_queue_get(f->inputs[0]))!=NULL){
887 888 889
		if (d->started){
			ms_mutex_lock(&d->mutex);
			ms_bufferizer_put(d->bufferizer,m);
890 891 892
			if (d->sleeping)
				ms_cond_signal(&d->cond);
			d->last_sample_date=f->ticker->time;
893 894
			ms_mutex_unlock(&d->mutex);
		}else freemsg(m);
jehan's avatar
jehan committed
895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917
	}
}


static MSFilterDesc msandroid_sound_write_desc={
/*.id=*/MS_FILTER_PLUGIN_ID,
/*.name=*/"MSAndSoundWrite",
/*.text=*/N_("Sound playback filter for Android"),
/*.category=*/MS_FILTER_OTHER,
/*.enc_fmt*/NULL,
/*.ninputs=*/1,
/*.noutputs=*/0,
/*.init*/NULL,
/*.preprocess=*/msandroid_sound_write_preprocess,
/*.process=*/msandroid_sound_write_process,
/*.postprocess=*/msandroid_sound_write_postprocess,
/*.uninit*/NULL,
/*.methods=*/msandroid_sound_write_methods
};


MSFilter *msandroid_sound_write_new(MSSndCard *card){
	ms_debug("msandroid_sound_write_new");
918
	MSFilter *f=ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &msandroid_sound_write_desc);
919
	msandroid_sound_write_data *data = new msandroid_sound_write_data();
920 921 922 923 924 925 926
	if (card->data != NULL) {
		SoundDeviceDescription *d = (SoundDeviceDescription *)card->data;
		if (d->recommended_rate > 0) {
			data->rate = d->recommended_rate;
			data->forced_rate = true;
			ms_warning("Using forced sample rate %i", data->rate);
		}
927 928
	}
	f->data = data;
jehan's avatar
jehan committed
929 930 931 932
	return f;
}


933

934

935 936
/******* Hack for Galaxy S ***********/

937 938 939 940
static int msandroid_hack_speaker_state(MSFilter *f, void *arg) {
	msandroid_sound_read_data *d=(msandroid_sound_read_data*)f->data;
	bool speakerOn = *((bool *)arg);

941 942
	if (!d->started) {
		ms_error("Audio recorder not started, can't hack speaker");
943
		return -1;
944 945 946 947 948
	}

	JNIEnv *jni_env = ms_get_jni_env();

	// First, check that required methods are found
949 950 951 952
	jclass LinphoneManager_class = (jclass)jni_env->NewGlobalRef(jni_env->FindClass("org/linphone/LinphoneManager"));
	if (LinphoneManager_class == 0) {
		ms_error("Cannot find org/linphone/LinphoneManager");
		return -1;
953
	}
954 955 956 957 958 959 960 961 962 963 964 965 966 967
	jclass LinphoneCoreImpl_class = (jclass)jni_env->NewGlobalRef(jni_env->FindClass("org/linphone/core/LinphoneCoreImpl"));
	if (LinphoneCoreImpl_class == 0) {
		ms_error("Cannot find org/linphone/core/LinphoneCoreImpl");
		return -1;
	}
	jmethodID getLc_id = jni_env->GetStaticMethodID(LinphoneManager_class, "getLc", "()Lorg/linphone/core/LinphoneCore;");
	if (getLc_id == 0) {
		ms_error("Cannot find LinphoneManager.getLc()");
		return -1;
	}
	jmethodID routeAudioToSpeakerHelper_id = jni_env->GetMethodID(LinphoneCoreImpl_class,"routeAudioToSpeakerHelper", "(Z)V");
	if (routeAudioToSpeakerHelper_id == 0) {
		ms_error("Cannot find LinphoneCoreImpl.routeAudioToSpeakerHelper()");
		return -1;
968
	}
969
	jobject lc = jni_env->CallStaticObjectMethod(LinphoneManager_class, getLc_id);
970 971

	ms_mutex_lock(&d->mutex);
972
	d->started = false;
973 974 975 976
	ms_mutex_unlock(&d->mutex);

	// Stop audio recorder
	ms_message("Hacking speaker state: calling sound_read_postprocess()");
977
	sound_read_postprocess(f);
978 979 980 981 982 983 984

	// Flush eventual sound in the buffer
	// No need to lock as reader_cb is stopped
	ms_bufferizer_flush(&d->rb);

	// Change speaker state by calling back to java code
	// as there seems to be no way to get a reference to AudioManager service.
985 986
	ms_message("Hacking speaker state: do magic from LinphoneCoreImpl.RouteAudioToSpeakerHelper()");
	jni_env->CallVoidMethod(lc, routeAudioToSpeakerHelper_id, speakerOn);
987 988 989

	// Re-open audio and set d->started=true
	ms_message("Hacking speaker state: calling sound_read_preprocess()");
990 991 992
	sound_read_preprocess(f);

	return 0;
993 994 995
}

/******* End Hack for Galaxy S ***********/