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