blob: 8b31996e3b297790ed8b95163c24396fa3403402 [file] [log] [blame]
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <stdio.h>
18#include <stdint.h>
19#include <string.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <sys/epoll.h>
23#include <sys/types.h>
24#include <sys/socket.h>
25#include <sys/stat.h>
26#include <sys/time.h>
27#include <time.h>
28#include <arpa/inet.h>
29#include <netinet/in.h>
30
31#define LOG_TAG "AudioGroup"
32#include <cutils/atomic.h>
Eric Laurentd7a724e2011-03-29 18:22:57 -070033#include <cutils/properties.h>
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080034#include <utils/Log.h>
35#include <utils/Errors.h>
36#include <utils/RefBase.h>
37#include <utils/threads.h>
38#include <utils/SystemClock.h>
39#include <media/AudioSystem.h>
40#include <media/AudioRecord.h>
41#include <media/AudioTrack.h>
42#include <media/mediarecorder.h>
43
Dima Zavin24fc2fb2011-04-19 22:30:36 -070044#include <hardware/audio.h>
45
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080046#include "jni.h"
47#include "JNIHelp.h"
48
49#include "AudioCodec.h"
Chia-chi Yeha8a10092010-10-05 01:17:13 +080050#include "EchoSuppressor.h"
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080051
52extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
53
54namespace {
55
56using namespace android;
57
58int gRandom = -1;
59
60// We use a circular array to implement jitter buffer. The simplest way is doing
61// a modulo operation on the index while accessing the array. However modulo can
62// be expensive on some platforms, such as ARM. Thus we round up the size of the
63// array to the nearest power of 2 and then use bitwise-and instead of modulo.
Chia-chi Yeh3520bd42010-09-30 13:48:07 +080064// Currently we make it 512ms long and assume packet interval is 40ms or less.
65// The first 80ms is the place where samples get mixed. The rest 432ms is the
66// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080067// are chosen by experiments and each of them can be adjusted as needed.
68
Chia-chi Yeh3cf71372011-01-04 19:10:06 +080069// Originally a stream does not send packets when it is receive-only or there is
70// nothing to mix. However, this causes some problems with certain firewalls and
71// proxies. A firewall might remove a port mapping when there is no outgoing
72// packet for a preiod of time, and a proxy might wait for incoming packets from
73// both sides before start forwarding. To solve these problems, we send out a
74// silence packet on the stream for every second. It should be good enough to
75// keep the stream alive with relatively low resources.
76
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080077// Other notes:
78// + We use elapsedRealtime() to get the time. Since we use 32bit variables
79// instead of 64bit ones, comparison must be done by subtraction.
80// + Sampling rate must be multiple of 1000Hz, and packet length must be in
81// milliseconds. No floating points.
82// + If we cannot get enough CPU, we drop samples and simulate packet loss.
83// + Resampling is not done yet, so streams in one group must use the same rate.
Chia-chi Yeh3520bd42010-09-30 13:48:07 +080084// For the first release only 8000Hz is supported.
85
86#define BUFFER_SIZE 512
87#define HISTORY_SIZE 80
88#define MEASURE_PERIOD 2000
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080089
90class AudioStream
91{
92public:
93 AudioStream();
94 ~AudioStream();
95 bool set(int mode, int socket, sockaddr_storage *remote,
Chia-chi Yeh4033a672010-09-16 18:36:45 +080096 AudioCodec *codec, int sampleRate, int sampleCount,
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +080097 int codecType, int dtmfType);
98
99 void sendDtmf(int event);
100 bool mix(int32_t *output, int head, int tail, int sampleRate);
101 void encode(int tick, AudioStream *chain);
102 void decode(int tick);
103
Chia-chi Yeh53aa6ef2010-11-30 13:10:31 +0800104private:
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800105 enum {
106 NORMAL = 0,
107 SEND_ONLY = 1,
108 RECEIVE_ONLY = 2,
109 LAST_MODE = 2,
110 };
111
112 int mMode;
113 int mSocket;
114 sockaddr_storage mRemote;
115 AudioCodec *mCodec;
116 uint32_t mCodecMagic;
117 uint32_t mDtmfMagic;
Chia-chi Yehfe529892010-09-30 02:42:27 +0800118 bool mFixRemote;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800119
120 int mTick;
121 int mSampleRate;
122 int mSampleCount;
123 int mInterval;
Chia-chi Yeh3cf71372011-01-04 19:10:06 +0800124 int mKeepAlive;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800125
126 int16_t *mBuffer;
127 int mBufferMask;
128 int mBufferHead;
129 int mBufferTail;
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800130 int mLatencyTimer;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800131 int mLatencyScore;
132
133 uint16_t mSequence;
134 uint32_t mTimestamp;
135 uint32_t mSsrc;
136
137 int mDtmfEvent;
138 int mDtmfStart;
139
140 AudioStream *mNext;
141
142 friend class AudioGroup;
143};
144
145AudioStream::AudioStream()
146{
147 mSocket = -1;
148 mCodec = NULL;
149 mBuffer = NULL;
150 mNext = NULL;
151}
152
153AudioStream::~AudioStream()
154{
155 close(mSocket);
156 delete mCodec;
157 delete [] mBuffer;
158 LOGD("stream[%d] is dead", mSocket);
159}
160
161bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800162 AudioCodec *codec, int sampleRate, int sampleCount,
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800163 int codecType, int dtmfType)
164{
165 if (mode < 0 || mode > LAST_MODE) {
166 return false;
167 }
168 mMode = mode;
169
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800170 mCodecMagic = (0x8000 | codecType) << 16;
171 mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16;
172
173 mTick = elapsedRealtime();
174 mSampleRate = sampleRate / 1000;
175 mSampleCount = sampleCount;
176 mInterval = mSampleCount / mSampleRate;
177
178 // Allocate jitter buffer.
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800179 for (mBufferMask = 8; mBufferMask < mSampleRate; mBufferMask <<= 1);
180 mBufferMask *= BUFFER_SIZE;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800181 mBuffer = new int16_t[mBufferMask];
182 --mBufferMask;
183 mBufferHead = 0;
184 mBufferTail = 0;
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800185 mLatencyTimer = 0;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800186 mLatencyScore = 0;
187
188 // Initialize random bits.
189 read(gRandom, &mSequence, sizeof(mSequence));
190 read(gRandom, &mTimestamp, sizeof(mTimestamp));
191 read(gRandom, &mSsrc, sizeof(mSsrc));
192
193 mDtmfEvent = -1;
194 mDtmfStart = 0;
195
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800196 // Only take over these things when succeeded.
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800197 mSocket = socket;
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800198 if (codec) {
199 mRemote = *remote;
200 mCodec = codec;
Chia-chi Yehfe529892010-09-30 02:42:27 +0800201
202 // Here we should never get an private address, but some buggy proxy
203 // servers do give us one. To solve this, we replace the address when
204 // the first time we successfully decode an incoming packet.
205 mFixRemote = false;
206 if (remote->ss_family == AF_INET) {
207 unsigned char *address =
208 (unsigned char *)&((sockaddr_in *)remote)->sin_addr;
209 if (address[0] == 10 ||
210 (address[0] == 172 && (address[1] >> 4) == 1) ||
211 (address[0] == 192 && address[1] == 168)) {
212 mFixRemote = true;
213 }
214 }
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800215 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800216
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800217 LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket,
218 (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800219 return true;
220}
221
222void AudioStream::sendDtmf(int event)
223{
224 if (mDtmfMagic != 0) {
225 mDtmfEvent = event << 24;
226 mDtmfStart = mTimestamp + mSampleCount;
227 }
228}
229
230bool AudioStream::mix(int32_t *output, int head, int tail, int sampleRate)
231{
232 if (mMode == SEND_ONLY) {
233 return false;
234 }
235
236 if (head - mBufferHead < 0) {
237 head = mBufferHead;
238 }
239 if (tail - mBufferTail > 0) {
240 tail = mBufferTail;
241 }
242 if (tail - head <= 0) {
243 return false;
244 }
245
246 head *= mSampleRate;
247 tail *= mSampleRate;
248
249 if (sampleRate == mSampleRate) {
250 for (int i = head; i - tail < 0; ++i) {
251 output[i - head] += mBuffer[i & mBufferMask];
252 }
253 } else {
254 // TODO: implement resampling.
255 return false;
256 }
257 return true;
258}
259
260void AudioStream::encode(int tick, AudioStream *chain)
261{
262 if (tick - mTick >= mInterval) {
263 // We just missed the train. Pretend that packets in between are lost.
264 int skipped = (tick - mTick) / mInterval;
265 mTick += skipped * mInterval;
266 mSequence += skipped;
267 mTimestamp += skipped * mSampleCount;
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800268 LOGV("stream[%d] skips %d packets", mSocket, skipped);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800269 }
270
271 tick = mTick;
272 mTick += mInterval;
273 ++mSequence;
274 mTimestamp += mSampleCount;
275
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800276 // If there is an ongoing DTMF event, send it now.
Chia-chi Yeh3cf71372011-01-04 19:10:06 +0800277 if (mMode != RECEIVE_ONLY && mDtmfEvent != -1) {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800278 int duration = mTimestamp - mDtmfStart;
279 // Make sure duration is reasonable.
280 if (duration >= 0 && duration < mSampleRate * 100) {
281 duration += mSampleCount;
282 int32_t buffer[4] = {
283 htonl(mDtmfMagic | mSequence),
284 htonl(mDtmfStart),
285 mSsrc,
286 htonl(mDtmfEvent | duration),
287 };
288 if (duration >= mSampleRate * 100) {
289 buffer[3] |= htonl(1 << 23);
290 mDtmfEvent = -1;
291 }
292 sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT,
293 (sockaddr *)&mRemote, sizeof(mRemote));
294 return;
295 }
296 mDtmfEvent = -1;
297 }
298
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800299 int32_t buffer[mSampleCount + 3];
Chia-chi Yeh3cf71372011-01-04 19:10:06 +0800300 int16_t samples[mSampleCount];
301 if (mMode == RECEIVE_ONLY) {
302 if ((mTick ^ mKeepAlive) >> 10 == 0) {
303 return;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800304 }
Chia-chi Yeh3cf71372011-01-04 19:10:06 +0800305 mKeepAlive = mTick;
306 memset(samples, 0, sizeof(samples));
307 } else {
308 // Mix all other streams.
309 bool mixed = false;
310 memset(buffer, 0, sizeof(buffer));
311 while (chain) {
312 if (chain != this &&
313 chain->mix(buffer, tick - mInterval, tick, mSampleRate)) {
314 mixed = true;
315 }
316 chain = chain->mNext;
317 }
318
319 if (mixed) {
320 // Saturate into 16 bits.
321 for (int i = 0; i < mSampleCount; ++i) {
322 int32_t sample = buffer[i];
323 if (sample < -32768) {
324 sample = -32768;
325 }
326 if (sample > 32767) {
327 sample = 32767;
328 }
329 samples[i] = sample;
330 }
331 } else {
332 if ((mTick ^ mKeepAlive) >> 10 == 0) {
333 return;
334 }
335 mKeepAlive = mTick;
336 memset(samples, 0, sizeof(samples));
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800337 LOGV("stream[%d] no data", mSocket);
repo sync7a69aef2010-09-23 05:46:01 +0800338 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800339 }
340
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800341 if (!mCodec) {
342 // Special case for device stream.
343 send(mSocket, samples, sizeof(samples), MSG_DONTWAIT);
344 return;
345 }
346
Chia-chi Yeh3cf71372011-01-04 19:10:06 +0800347 // Cook the packet and send it out.
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800348 buffer[0] = htonl(mCodecMagic | mSequence);
349 buffer[1] = htonl(mTimestamp);
350 buffer[2] = mSsrc;
351 int length = mCodec->encode(&buffer[3], samples);
352 if (length <= 0) {
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800353 LOGV("stream[%d] encoder error", mSocket);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800354 return;
355 }
356 sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
357 sizeof(mRemote));
358}
359
360void AudioStream::decode(int tick)
361{
362 char c;
363 if (mMode == SEND_ONLY) {
364 recv(mSocket, &c, 1, MSG_DONTWAIT);
365 return;
366 }
367
368 // Make sure mBufferHead and mBufferTail are reasonable.
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800369 if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) {
370 mBufferHead = tick - HISTORY_SIZE;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800371 mBufferTail = mBufferHead;
372 }
373
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800374 if (tick - mBufferHead > HISTORY_SIZE) {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800375 // Throw away outdated samples.
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800376 mBufferHead = tick - HISTORY_SIZE;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800377 if (mBufferTail - mBufferHead < 0) {
378 mBufferTail = mBufferHead;
379 }
380 }
381
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800382 // Adjust the jitter buffer if the latency keeps larger than two times of the
383 // packet interval in the past two seconds.
384 int score = mBufferTail - tick - mInterval * 2;
385 if (mLatencyScore > score) {
386 mLatencyScore = score;
387 }
388 if (mLatencyScore <= 0) {
389 mLatencyTimer = tick;
390 mLatencyScore = score;
391 } else if (tick - mLatencyTimer >= MEASURE_PERIOD) {
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800392 LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800393 mBufferTail -= mLatencyScore;
394 mLatencyTimer = tick;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800395 }
396
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800397 if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800398 // Buffer overflow. Drop the packet.
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800399 LOGV("stream[%d] buffer overflow", mSocket);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800400 recv(mSocket, &c, 1, MSG_DONTWAIT);
401 return;
402 }
403
404 // Receive the packet and decode it.
405 int16_t samples[mSampleCount];
406 int length = 0;
407 if (!mCodec) {
408 // Special case for device stream.
409 length = recv(mSocket, samples, sizeof(samples),
410 MSG_TRUNC | MSG_DONTWAIT) >> 1;
411 } else {
412 __attribute__((aligned(4))) uint8_t buffer[2048];
Chia-chi Yehfe529892010-09-30 02:42:27 +0800413 sockaddr_storage remote;
414 socklen_t len = sizeof(remote);
Chung-yih Wangbd229422010-09-23 23:23:11 +0800415
Chia-chi Yehfe529892010-09-30 02:42:27 +0800416 length = recvfrom(mSocket, buffer, sizeof(buffer),
417 MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &len);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800418
419 // Do we need to check SSRC, sequence, and timestamp? They are not
Chia-chi Yehb8790322010-08-19 18:26:53 +0800420 // reliable but at least they can be used to identify duplicates?
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800421 if (length < 12 || length > (int)sizeof(buffer) ||
422 (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800423 LOGV("stream[%d] malformed packet", mSocket);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800424 return;
425 }
426 int offset = 12 + ((buffer[0] & 0x0F) << 2);
427 if ((buffer[0] & 0x10) != 0) {
428 offset += 4 + (ntohs(*(uint16_t *)&buffer[offset + 2]) << 2);
429 }
430 if ((buffer[0] & 0x20) != 0) {
431 length -= buffer[length - 1];
432 }
433 length -= offset;
434 if (length >= 0) {
435 length = mCodec->decode(samples, &buffer[offset], length);
436 }
Chia-chi Yehfe529892010-09-30 02:42:27 +0800437 if (length > 0 && mFixRemote) {
438 mRemote = remote;
439 mFixRemote = false;
440 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800441 }
Chia-chi Yehfe529892010-09-30 02:42:27 +0800442 if (length <= 0) {
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800443 LOGV("stream[%d] decoder error", mSocket);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800444 return;
445 }
446
447 if (tick - mBufferTail > 0) {
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800448 // Buffer underrun. Reset the jitter buffer.
Chia-chi Yeh21ae1ad2010-09-30 16:07:44 +0800449 LOGV("stream[%d] buffer underrun", mSocket);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800450 if (mBufferTail - mBufferHead <= 0) {
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800451 mBufferHead = tick + mInterval;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800452 mBufferTail = mBufferHead;
453 } else {
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800454 int tail = (tick + mInterval) * mSampleRate;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800455 for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) {
456 mBuffer[i & mBufferMask] = 0;
457 }
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800458 mBufferTail = tick + mInterval;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800459 }
460 }
461
462 // Append to the jitter buffer.
463 int tail = mBufferTail * mSampleRate;
464 for (int i = 0; i < mSampleCount; ++i) {
465 mBuffer[tail & mBufferMask] = samples[i];
466 ++tail;
467 }
468 mBufferTail += mInterval;
469}
470
471//------------------------------------------------------------------------------
472
473class AudioGroup
474{
475public:
476 AudioGroup();
477 ~AudioGroup();
478 bool set(int sampleRate, int sampleCount);
479
480 bool setMode(int mode);
481 bool sendDtmf(int event);
482 bool add(AudioStream *stream);
483 bool remove(int socket);
484
Chia-chi Yeh53aa6ef2010-11-30 13:10:31 +0800485private:
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800486 enum {
487 ON_HOLD = 0,
488 MUTED = 1,
489 NORMAL = 2,
Chia-chi Yehd87be272011-01-06 17:43:24 +0800490 ECHO_SUPPRESSION = 3,
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800491 LAST_MODE = 3,
492 };
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800493
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800494 AudioStream *mChain;
495 int mEventQueue;
496 volatile int mDtmfEvent;
497
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800498 int mMode;
499 int mSampleRate;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800500 int mSampleCount;
501 int mDeviceSocket;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800502
503 class NetworkThread : public Thread
504 {
505 public:
506 NetworkThread(AudioGroup *group) : Thread(false), mGroup(group) {}
507
508 bool start()
509 {
510 if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) {
511 LOGE("cannot start network thread");
512 return false;
513 }
514 return true;
515 }
516
517 private:
518 AudioGroup *mGroup;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800519 bool threadLoop();
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800520 };
521 sp<NetworkThread> mNetworkThread;
522
523 class DeviceThread : public Thread
524 {
525 public:
526 DeviceThread(AudioGroup *group) : Thread(false), mGroup(group) {}
527
528 bool start()
529 {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800530 if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) {
531 LOGE("cannot start device thread");
532 return false;
533 }
534 return true;
535 }
536
537 private:
538 AudioGroup *mGroup;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800539 bool threadLoop();
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800540 };
541 sp<DeviceThread> mDeviceThread;
542};
543
544AudioGroup::AudioGroup()
545{
546 mMode = ON_HOLD;
547 mChain = NULL;
548 mEventQueue = -1;
549 mDtmfEvent = -1;
550 mDeviceSocket = -1;
551 mNetworkThread = new NetworkThread(this);
552 mDeviceThread = new DeviceThread(this);
553}
554
555AudioGroup::~AudioGroup()
556{
557 mNetworkThread->requestExitAndWait();
558 mDeviceThread->requestExitAndWait();
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800559 close(mEventQueue);
560 close(mDeviceSocket);
561 while (mChain) {
562 AudioStream *next = mChain->mNext;
563 delete mChain;
564 mChain = next;
565 }
566 LOGD("group[%d] is dead", mDeviceSocket);
567}
568
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800569bool AudioGroup::set(int sampleRate, int sampleCount)
570{
571 mEventQueue = epoll_create(2);
572 if (mEventQueue == -1) {
573 LOGE("epoll_create: %s", strerror(errno));
574 return false;
575 }
576
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800577 mSampleRate = sampleRate;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800578 mSampleCount = sampleCount;
579
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800580 // Create device socket.
581 int pair[2];
582 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) {
583 LOGE("socketpair: %s", strerror(errno));
584 return false;
585 }
586 mDeviceSocket = pair[0];
587
588 // Create device stream.
589 mChain = new AudioStream;
590 if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL,
591 sampleRate, sampleCount, -1, -1)) {
592 close(pair[1]);
593 LOGE("cannot initialize device stream");
594 return false;
595 }
596
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800597 // Give device socket a reasonable timeout.
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800598 timeval tv;
599 tv.tv_sec = 0;
Chia-chi Yeh557b04d2010-09-08 09:56:02 +0800600 tv.tv_usec = 1000 * sampleCount / sampleRate * 500;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800601 if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800602 LOGE("setsockopt: %s", strerror(errno));
603 return false;
604 }
605
606 // Add device stream into event queue.
607 epoll_event event;
608 event.events = EPOLLIN;
609 event.data.ptr = mChain;
610 if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) {
611 LOGE("epoll_ctl: %s", strerror(errno));
612 return false;
613 }
614
615 // Anything else?
616 LOGD("stream[%d] joins group[%d]", pair[1], pair[0]);
617 return true;
618}
619
620bool AudioGroup::setMode(int mode)
621{
622 if (mode < 0 || mode > LAST_MODE) {
623 return false;
624 }
Eric Laurentd7a724e2011-03-29 18:22:57 -0700625 //FIXME: temporary code to overcome echo and mic gain issues on herring board.
626 // Must be modified/removed when proper support for voice processing query and control
627 // is included in audio framework
628 char value[PROPERTY_VALUE_MAX];
629 property_get("ro.product.board", value, "");
630 if (mode == NORMAL && !strcmp(value, "herring")) {
631 mode = ECHO_SUPPRESSION;
632 }
Chia-chi Yehd87be272011-01-06 17:43:24 +0800633 if (mode == ECHO_SUPPRESSION && AudioSystem::getParameters(
634 0, String8("ec_supported")) == "ec_supported=yes") {
635 mode = NORMAL;
636 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800637 if (mMode == mode) {
638 return true;
639 }
640
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800641 mDeviceThread->requestExitAndWait();
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800642 LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode);
643 mMode = mode;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800644 return (mode == ON_HOLD) || mDeviceThread->start();
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800645}
646
647bool AudioGroup::sendDtmf(int event)
648{
649 if (event < 0 || event > 15) {
650 return false;
651 }
652
653 // DTMF is rarely used, so we try to make it as lightweight as possible.
654 // Using volatile might be dodgy, but using a pipe or pthread primitives
655 // or stop-set-restart threads seems too heavy. Will investigate later.
656 timespec ts;
657 ts.tv_sec = 0;
658 ts.tv_nsec = 100000000;
659 for (int i = 0; mDtmfEvent != -1 && i < 20; ++i) {
660 nanosleep(&ts, NULL);
661 }
662 if (mDtmfEvent != -1) {
663 return false;
664 }
665 mDtmfEvent = event;
666 nanosleep(&ts, NULL);
667 return true;
668}
669
670bool AudioGroup::add(AudioStream *stream)
671{
672 mNetworkThread->requestExitAndWait();
673
674 epoll_event event;
675 event.events = EPOLLIN;
676 event.data.ptr = stream;
677 if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) {
678 LOGE("epoll_ctl: %s", strerror(errno));
679 return false;
680 }
681
682 stream->mNext = mChain->mNext;
683 mChain->mNext = stream;
684 if (!mNetworkThread->start()) {
685 // Only take over the stream when succeeded.
686 mChain->mNext = stream->mNext;
687 return false;
688 }
689
690 LOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket);
691 return true;
692}
693
694bool AudioGroup::remove(int socket)
695{
696 mNetworkThread->requestExitAndWait();
697
698 for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) {
699 AudioStream *target = stream->mNext;
700 if (target->mSocket == socket) {
Chia-chi Yehb8790322010-08-19 18:26:53 +0800701 if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, socket, NULL)) {
702 LOGE("epoll_ctl: %s", strerror(errno));
703 return false;
704 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800705 stream->mNext = target->mNext;
706 LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket);
707 delete target;
708 break;
709 }
710 }
711
712 // Do not start network thread if there is only one stream.
713 if (!mChain->mNext || !mNetworkThread->start()) {
714 return false;
715 }
716 return true;
717}
718
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800719bool AudioGroup::NetworkThread::threadLoop()
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800720{
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800721 AudioStream *chain = mGroup->mChain;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800722 int tick = elapsedRealtime();
723 int deadline = tick + 10;
724 int count = 0;
725
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800726 for (AudioStream *stream = chain; stream; stream = stream->mNext) {
Chia-chi Yeh3520bd42010-09-30 13:48:07 +0800727 if (tick - stream->mTick >= 0) {
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800728 stream->encode(tick, chain);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800729 }
730 if (deadline - stream->mTick > 0) {
731 deadline = stream->mTick;
732 }
733 ++count;
734 }
735
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800736 int event = mGroup->mDtmfEvent;
737 if (event != -1) {
738 for (AudioStream *stream = chain; stream; stream = stream->mNext) {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800739 stream->sendDtmf(event);
740 }
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800741 mGroup->mDtmfEvent = -1;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800742 }
743
744 deadline -= tick;
745 if (deadline < 1) {
746 deadline = 1;
747 }
748
749 epoll_event events[count];
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800750 count = epoll_wait(mGroup->mEventQueue, events, count, deadline);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800751 if (count == -1) {
752 LOGE("epoll_wait: %s", strerror(errno));
753 return false;
754 }
755 for (int i = 0; i < count; ++i) {
756 ((AudioStream *)events[i].data.ptr)->decode(tick);
757 }
758
759 return true;
760}
761
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800762bool AudioGroup::DeviceThread::threadLoop()
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800763{
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800764 int mode = mGroup->mMode;
765 int sampleRate = mGroup->mSampleRate;
766 int sampleCount = mGroup->mSampleCount;
767 int deviceSocket = mGroup->mDeviceSocket;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800768
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800769 // Find out the frame count for AudioTrack and AudioRecord.
770 int output = 0;
771 int input = 0;
Dima Zavin24fc2fb2011-04-19 22:30:36 -0700772 if (AudioTrack::getMinFrameCount(&output, AUDIO_STREAM_VOICE_CALL,
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800773 sampleRate) != NO_ERROR || output <= 0 ||
774 AudioRecord::getMinFrameCount(&input, sampleRate,
Dima Zavin24fc2fb2011-04-19 22:30:36 -0700775 AUDIO_FORMAT_PCM_16_BIT, 1) != NO_ERROR || input <= 0) {
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800776 LOGE("cannot compute frame count");
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800777 return false;
778 }
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800779 LOGD("reported frame count: output %d, input %d", output, input);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800780
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800781 if (output < sampleCount * 2) {
782 output = sampleCount * 2;
783 }
784 if (input < sampleCount * 2) {
785 input = sampleCount * 2;
786 }
787 LOGD("adjusted frame count: output %d, input %d", output, input);
788
789 // Initialize AudioTrack and AudioRecord.
790 AudioTrack track;
791 AudioRecord record;
Dima Zavin24fc2fb2011-04-19 22:30:36 -0700792 if (track.set(AUDIO_STREAM_VOICE_CALL, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
793 AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set(
794 AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
795 AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) {
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800796 LOGE("cannot initialize audio device");
797 return false;
798 }
799 LOGD("latency: output %d, input %d", track.latency(), record.latency());
800
Chia-chi Yeha8a10092010-10-05 01:17:13 +0800801 // Initialize echo canceler.
Chia-chi Yeh8a68b522010-10-21 23:39:35 +0800802 EchoSuppressor echo(sampleCount,
Chia-chi Yeha8a10092010-10-05 01:17:13 +0800803 (track.latency() + record.latency()) * sampleRate / 1000);
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800804
805 // Give device socket a reasonable buffer size.
806 setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output));
807 setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output));
808
809 // Drain device socket.
810 char c;
811 while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
812
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800813 // Start AudioRecord before AudioTrack. This prevents AudioTrack from being
814 // disabled due to buffer underrun while waiting for AudioRecord.
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800815 if (mode != MUTED) {
816 record.start();
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800817 int16_t one;
818 record.read(&one, sizeof(one));
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800819 }
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800820 track.start();
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800821
822 while (!exitPending()) {
823 int16_t output[sampleCount];
824 if (recv(deviceSocket, output, sizeof(output), 0) <= 0) {
825 memset(output, 0, sizeof(output));
826 }
827
828 int16_t input[sampleCount];
829 int toWrite = sampleCount;
830 int toRead = (mode == MUTED) ? 0 : sampleCount;
831 int chances = 100;
832
833 while (--chances > 0 && (toWrite > 0 || toRead > 0)) {
834 if (toWrite > 0) {
835 AudioTrack::Buffer buffer;
836 buffer.frameCount = toWrite;
837
838 status_t status = track.obtainBuffer(&buffer, 1);
839 if (status == NO_ERROR) {
840 int offset = sampleCount - toWrite;
841 memcpy(buffer.i8, &output[offset], buffer.size);
842 toWrite -= buffer.frameCount;
843 track.releaseBuffer(&buffer);
844 } else if (status != TIMED_OUT && status != WOULD_BLOCK) {
845 LOGE("cannot write to AudioTrack");
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800846 return true;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800847 }
848 }
849
850 if (toRead > 0) {
851 AudioRecord::Buffer buffer;
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800852 buffer.frameCount = toRead;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800853
854 status_t status = record.obtainBuffer(&buffer, 1);
855 if (status == NO_ERROR) {
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800856 int offset = sampleCount - toRead;
857 memcpy(&input[offset], buffer.i8, buffer.size);
858 toRead -= buffer.frameCount;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800859 record.releaseBuffer(&buffer);
860 } else if (status != TIMED_OUT && status != WOULD_BLOCK) {
861 LOGE("cannot read from AudioRecord");
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800862 return true;
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800863 }
864 }
865 }
866
867 if (chances <= 0) {
Chia-chi Yeh67ecb5b2010-10-01 08:20:09 +0800868 LOGW("device loop timeout");
869 while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800870 }
871
872 if (mode != MUTED) {
873 if (mode == NORMAL) {
874 send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
875 } else {
Chia-chi Yeha8a10092010-10-05 01:17:13 +0800876 echo.run(output, input);
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800877 send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
878 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800879 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800880 }
Chia-chi Yeh9083c842010-09-29 05:19:44 +0800881 return false;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800882}
883
884//------------------------------------------------------------------------------
885
886static jfieldID gNative;
887static jfieldID gMode;
888
Chia-chi Yehb8790322010-08-19 18:26:53 +0800889void add(JNIEnv *env, jobject thiz, jint mode,
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800890 jint socket, jstring jRemoteAddress, jint remotePort,
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800891 jstring jCodecSpec, jint dtmfType)
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800892{
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800893 AudioCodec *codec = NULL;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800894 AudioStream *stream = NULL;
895 AudioGroup *group = NULL;
896
897 // Sanity check.
898 sockaddr_storage remote;
899 if (parse(env, jRemoteAddress, remotePort, &remote) < 0) {
900 // Exception already thrown.
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800901 return;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800902 }
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800903 if (!jCodecSpec) {
904 jniThrowNullPointerException(env, "codecSpec");
905 return;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800906 }
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800907 const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL);
908 if (!codecSpec) {
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800909 // Exception already thrown.
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800910 return;
911 }
912
913 // Create audio codec.
914 int codecType = -1;
915 char codecName[16];
916 int sampleRate = -1;
Chia-chi Yeh3cf71372011-01-04 19:10:06 +0800917 sscanf(codecSpec, "%d %15[^/]%*c%d", &codecType, codecName, &sampleRate);
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800918 codec = newAudioCodec(codecName);
919 int sampleCount = (codec ? codec->set(sampleRate, codecSpec) : -1);
920 env->ReleaseStringUTFChars(jCodecSpec, codecSpec);
921 if (sampleCount <= 0) {
922 jniThrowException(env, "java/lang/IllegalStateException",
923 "cannot initialize audio codec");
Chia-chi Yehb8790322010-08-19 18:26:53 +0800924 goto error;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800925 }
926
927 // Create audio stream.
928 stream = new AudioStream;
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800929 if (!stream->set(mode, socket, &remote, codec, sampleRate, sampleCount,
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800930 codecType, dtmfType)) {
931 jniThrowException(env, "java/lang/IllegalStateException",
932 "cannot initialize audio stream");
933 goto error;
934 }
935 socket = -1;
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800936 codec = NULL;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800937
938 // Create audio group.
939 group = (AudioGroup *)env->GetIntField(thiz, gNative);
940 if (!group) {
941 int mode = env->GetIntField(thiz, gMode);
942 group = new AudioGroup;
943 if (!group->set(8000, 256) || !group->setMode(mode)) {
944 jniThrowException(env, "java/lang/IllegalStateException",
945 "cannot initialize audio group");
946 goto error;
947 }
948 }
949
950 // Add audio stream into audio group.
951 if (!group->add(stream)) {
952 jniThrowException(env, "java/lang/IllegalStateException",
953 "cannot add audio stream");
954 goto error;
955 }
956
957 // Succeed.
958 env->SetIntField(thiz, gNative, (int)group);
Chia-chi Yehb8790322010-08-19 18:26:53 +0800959 return;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800960
961error:
962 delete group;
963 delete stream;
Chia-chi Yeh4033a672010-09-16 18:36:45 +0800964 delete codec;
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800965 close(socket);
966 env->SetIntField(thiz, gNative, NULL);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800967}
968
969void remove(JNIEnv *env, jobject thiz, jint socket)
970{
971 AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
972 if (group) {
973 if (socket == -1 || !group->remove(socket)) {
974 delete group;
975 env->SetIntField(thiz, gNative, NULL);
976 }
977 }
978}
979
980void setMode(JNIEnv *env, jobject thiz, jint mode)
981{
982 AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
983 if (group && !group->setMode(mode)) {
984 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800985 }
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +0800986}
987
988void sendDtmf(JNIEnv *env, jobject thiz, jint event)
989{
990 AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
991 if (group && !group->sendDtmf(event)) {
992 jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
993 }
994}
995
996JNINativeMethod gMethods[] = {
Chia-chi Yeh53aa6ef2010-11-30 13:10:31 +0800997 {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add},
998 {"nativeRemove", "(I)V", (void *)remove},
999 {"nativeSetMode", "(I)V", (void *)setMode},
1000 {"nativeSendDtmf", "(I)V", (void *)sendDtmf},
Chia-chi Yeh4c5d28c2010-08-06 14:12:05 +08001001};
1002
1003} // namespace
1004
1005int registerAudioGroup(JNIEnv *env)
1006{
1007 gRandom = open("/dev/urandom", O_RDONLY);
1008 if (gRandom == -1) {
1009 LOGE("urandom: %s", strerror(errno));
1010 return -1;
1011 }
1012
1013 jclass clazz;
1014 if ((clazz = env->FindClass("android/net/rtp/AudioGroup")) == NULL ||
1015 (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL ||
1016 (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL ||
1017 env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
1018 LOGE("JNI registration failed");
1019 return -1;
1020 }
1021 return 0;
1022}