198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang/* 298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Copyright (C) 2010 The Android Open Source Project 398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * 498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Licensed under the Apache License, Version 2.0 (the "License"); 598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * you may not use this file except in compliance with the License. 698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * You may obtain a copy of the License at 798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * 898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * http://www.apache.org/licenses/LICENSE-2.0 998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * 1098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Unless required by applicable law or agreed to in writing, software 1198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * distributed under the License is distributed on an "AS IS" BASIS, 1298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * See the License for the specific language governing permissions and 1498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * limitations under the License. 1598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang */ 1698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 1798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangpackage android.net.rtp; 1898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 19c2d55f13a21fe6a288601deab6832c704c998781Svet Ganovimport android.app.ActivityThread; 200a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yehimport android.media.AudioManager; 210a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh 2298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.util.HashMap; 23fe2164c76a5715e0a8106aacfd78a372d510bc04Johan Redestigimport java.util.Locale; 2498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangimport java.util.Map; 2598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 2698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang/** 270a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * An AudioGroup is an audio hub for the speaker, the microphone, and 280a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * {@link AudioStream}s. Each of these components can be logically turned on 290a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}. 300a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * The AudioGroup will go through these components and process them one by one 310a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * within its execution loop. The loop consists of four steps. First, for each 320a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming 330a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * packets and stores in its buffer. Then, if the microphone is enabled, 340a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * processes the recorded audio and stores in its buffer. Third, if the speaker 350a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * is enabled, mixes all AudioStream buffers and plays back. Finally, for each 360a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other 370a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * buffers and sends back the encoded packets. An AudioGroup does nothing if 380a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * there is no AudioStream in it. 397f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * 407f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * <p>Few things must be noticed before using these classes. The performance is 417f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * highly related to the system load and the network bandwidth. Usually a 427f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network 430a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * bandwidth, and vise versa. Using two AudioStreams at the same time doubles 440a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * not only the load but also the bandwidth. The condition varies from one 450a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * device to another, and developers should choose the right combination in 460a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * order to get the best result.</p> 477f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * 487f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For 497f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * example, a Voice over IP (VoIP) application might want to put a conference 507f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * call on hold in order to make a new call but still allow people in the 510a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * conference call talking to each other. This can be done easily using two 527f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * AudioGroups, but there are some limitations. Since the speaker and the 530a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * microphone are globally shared resources, only one AudioGroup at a time is 540a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will 550a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * be unable to acquire these resources and fail silently.</p> 56a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh * 57a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh * <p class="note">Using this class requires 580a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers 590a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION} 600a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * using {@link AudioManager#setMode(int)} and change it back when none of 610a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * the AudioGroups is in use.</p> 62a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh * 63a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh * @see AudioStream 6498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang */ 6598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wangpublic class AudioGroup { 667f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 677f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * This mode is similar to {@link #MODE_NORMAL} except the speaker and 680a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * the microphone are both disabled. 697f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 7098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang public static final int MODE_ON_HOLD = 0; 717f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh 727f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 737f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * This mode is similar to {@link #MODE_NORMAL} except the microphone is 740a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh * disabled. 757f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 7698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang public static final int MODE_MUTED = 1; 777f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh 787f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 797f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * This mode indicates that the speaker, the microphone, and all 807f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * {@link AudioStream}s in the group are enabled. First, the packets 817f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * received from the streams are decoded and mixed with the audio recorded 827f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * from the microphone. Then, the results are played back to the speaker, 837f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * encoded and sent back to each stream. 847f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 8598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang public static final int MODE_NORMAL = 2; 867f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh 877f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 887f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * This mode is similar to {@link #MODE_NORMAL} except the echo suppression 897f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * is enabled. It should be only used when the speaker phone is on. 907f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 917f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh public static final int MODE_ECHO_SUPPRESSION = 3; 9298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 93a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh private static final int MODE_LAST = 3; 94a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh 95ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat private final Map<AudioStream, Long> mStreams; 9698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang private int mMode = MODE_ON_HOLD; 9798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 98ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat private long mNative; 9998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang static { 10098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang System.loadLibrary("rtp_jni"); 10198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 10298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 1037f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 1047f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * Creates an empty AudioGroup. 1057f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 10698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang public AudioGroup() { 107ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat mStreams = new HashMap<AudioStream, Long>(); 10898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 10998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 1107f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 111a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh * Returns the {@link AudioStream}s in this group. 112a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh */ 113a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh public AudioStream[] getStreams() { 114a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh synchronized (this) { 115a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh return mStreams.keySet().toArray(new AudioStream[mStreams.size()]); 116a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 117a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 118a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh 119a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh /** 1207f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * Returns the current mode. 1217f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 12298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang public int getMode() { 12398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang return mMode; 12498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 12598cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 1267f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 1277f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * Changes the current mode. It must be one of {@link #MODE_ON_HOLD}, 1287f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and 1297f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * {@link #MODE_ECHO_SUPPRESSION}. 1307f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * 1317f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * @param mode The mode to change to. 1327f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * @throws IllegalArgumentException if the mode is invalid. 1337f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 134a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh public void setMode(int mode) { 135a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh if (mode < 0 || mode > MODE_LAST) { 136a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh throw new IllegalArgumentException("Invalid mode"); 137a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 138a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh synchronized (this) { 139a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh nativeSetMode(mode); 140a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh mMode = mode; 141a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 142a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 1437f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh 144a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh private native void nativeSetMode(int mode); 145a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh 146a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh // Package-private method used by AudioStream.join(). 1472bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh synchronized void add(AudioStream stream) { 1480a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh if (!mStreams.containsKey(stream)) { 1490a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh try { 1502bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh AudioCodec codec = stream.getCodec(); 151fe2164c76a5715e0a8106aacfd78a372d510bc04Johan Redestig String codecSpec = String.format(Locale.US, "%d %s %s", codec.type, 1520a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh codec.rtpmap, codec.fmtp); 153ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat long id = nativeAdd(stream.getMode(), stream.getSocket(), 1540a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh stream.getRemoteAddress().getHostAddress(), 155c2d55f13a21fe6a288601deab6832c704c998781Svet Ganov stream.getRemotePort(), codecSpec, stream.getDtmfType(), 156b65349013b2ee185dd7e0793ff6cbcbd76affa3eSvet Ganov ActivityThread.currentOpPackageName()); 1572bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh mStreams.put(stream, id); 1580a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh } catch (NullPointerException e) { 1590a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh throw new IllegalStateException(e); 16098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 16198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 16298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 16398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 164ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat private native long nativeAdd(int mode, int socket, String remoteAddress, 165c2d55f13a21fe6a288601deab6832c704c998781Svet Ganov int remotePort, String codecSpec, int dtmfType, String opPackageName); 16698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 167a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh // Package-private method used by AudioStream.join(). 1680a3e1f1851b26657b865ae3c91200c59dfc411feChia-chi Yeh synchronized void remove(AudioStream stream) { 169ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat Long id = mStreams.remove(stream); 1702bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh if (id != null) { 1712bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh nativeRemove(id); 17298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 17398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 17498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 175ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat private native void nativeRemove(long id); 176a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh 17798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang /** 17898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * Sends a DTMF digit to every {@link AudioStream} in this group. Currently 17998cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * only event {@code 0} to {@code 15} are supported. 18098cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * 18198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang * @throws IllegalArgumentException if the event is invalid. 18298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang */ 183a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh public void sendDtmf(int event) { 184a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh if (event < 0 || event > 15) { 185a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh throw new IllegalArgumentException("Invalid event"); 186a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 187a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh synchronized (this) { 188a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh nativeSendDtmf(event); 189a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 190a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 191a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh 192a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh private native void nativeSendDtmf(int event); 19398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 1947f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh /** 1957f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh * Removes every {@link AudioStream} in this group. 1967f383063fafc54a41f91540a41bf987003fd2502Chia-chi Yeh */ 197a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh public void clear() { 1982bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh for (AudioStream stream : getStreams()) { 1992bf2e642d061e7b48dd71927752e9151a5126fb2Chia-chi Yeh stream.join(null); 200a54e36694b3909ef367867eb7516b2d6f6031261Chia-chi Yeh } 20198cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 20298cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang 20398cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang @Override 20498cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang protected void finalize() throws Throwable { 205ae710cd10213c0a89e9daea945f3efa9f90b188eAshok Bhat nativeRemove(0L); 20698cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang super.finalize(); 20798cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang } 20898cee0ce2354234e72bafb836864ec10a490ea4dChung-yih Wang} 209