Home | History | Annotate | Download | only in rtp
      1 /*
      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 package android.net.rtp;
     18 
     19 import android.app.ActivityThread;
     20 import android.media.AudioManager;
     21 
     22 import java.util.HashMap;
     23 import java.util.Locale;
     24 import java.util.Map;
     25 
     26 /**
     27  * An AudioGroup is an audio hub for the speaker, the microphone, and
     28  * {@link AudioStream}s. Each of these components can be logically turned on
     29  * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}.
     30  * The AudioGroup will go through these components and process them one by one
     31  * within its execution loop. The loop consists of four steps. First, for each
     32  * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
     33  * packets and stores in its buffer. Then, if the microphone is enabled,
     34  * processes the recorded audio and stores in its buffer. Third, if the speaker
     35  * is enabled, mixes all AudioStream buffers and plays back. Finally, for each
     36  * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
     37  * buffers and sends back the encoded packets. An AudioGroup does nothing if
     38  * there is no AudioStream in it.
     39  *
     40  * <p>Few things must be noticed before using these classes. The performance is
     41  * highly related to the system load and the network bandwidth. Usually a
     42  * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network
     43  * bandwidth, and vise versa. Using two AudioStreams at the same time doubles
     44  * not only the load but also the bandwidth. The condition varies from one
     45  * device to another, and developers should choose the right combination in
     46  * order to get the best result.</p>
     47  *
     48  * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For
     49  * example, a Voice over IP (VoIP) application might want to put a conference
     50  * call on hold in order to make a new call but still allow people in the
     51  * conference call talking to each other. This can be done easily using two
     52  * AudioGroups, but there are some limitations. Since the speaker and the
     53  * microphone are globally shared resources, only one AudioGroup at a time is
     54  * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will
     55  * be unable to acquire these resources and fail silently.</p>
     56  *
     57  * <p class="note">Using this class requires
     58  * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers
     59  * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION}
     60  * using {@link AudioManager#setMode(int)} and change it back when none of
     61  * the AudioGroups is in use.</p>
     62  *
     63  * @see AudioStream
     64  */
     65 public class AudioGroup {
     66     /**
     67      * This mode is similar to {@link #MODE_NORMAL} except the speaker and
     68      * the microphone are both disabled.
     69      */
     70     public static final int MODE_ON_HOLD = 0;
     71 
     72     /**
     73      * This mode is similar to {@link #MODE_NORMAL} except the microphone is
     74      * disabled.
     75      */
     76     public static final int MODE_MUTED = 1;
     77 
     78     /**
     79      * This mode indicates that the speaker, the microphone, and all
     80      * {@link AudioStream}s in the group are enabled. First, the packets
     81      * received from the streams are decoded and mixed with the audio recorded
     82      * from the microphone. Then, the results are played back to the speaker,
     83      * encoded and sent back to each stream.
     84      */
     85     public static final int MODE_NORMAL = 2;
     86 
     87     /**
     88      * This mode is similar to {@link #MODE_NORMAL} except the echo suppression
     89      * is enabled. It should be only used when the speaker phone is on.
     90      */
     91     public static final int MODE_ECHO_SUPPRESSION = 3;
     92 
     93     private static final int MODE_LAST = 3;
     94 
     95     private final Map<AudioStream, Long> mStreams;
     96     private int mMode = MODE_ON_HOLD;
     97 
     98     private long mNative;
     99     static {
    100         System.loadLibrary("rtp_jni");
    101     }
    102 
    103     /**
    104      * Creates an empty AudioGroup.
    105      */
    106     public AudioGroup() {
    107         mStreams = new HashMap<AudioStream, Long>();
    108     }
    109 
    110     /**
    111      * Returns the {@link AudioStream}s in this group.
    112      */
    113     public AudioStream[] getStreams() {
    114         synchronized (this) {
    115             return mStreams.keySet().toArray(new AudioStream[mStreams.size()]);
    116         }
    117     }
    118 
    119     /**
    120      * Returns the current mode.
    121      */
    122     public int getMode() {
    123         return mMode;
    124     }
    125 
    126     /**
    127      * Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
    128      * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
    129      * {@link #MODE_ECHO_SUPPRESSION}.
    130      *
    131      * @param mode The mode to change to.
    132      * @throws IllegalArgumentException if the mode is invalid.
    133      */
    134     public void setMode(int mode) {
    135         if (mode < 0 || mode > MODE_LAST) {
    136             throw new IllegalArgumentException("Invalid mode");
    137         }
    138         synchronized (this) {
    139             nativeSetMode(mode);
    140             mMode = mode;
    141         }
    142     }
    143 
    144     private native void nativeSetMode(int mode);
    145 
    146     // Package-private method used by AudioStream.join().
    147     synchronized void add(AudioStream stream) {
    148         if (!mStreams.containsKey(stream)) {
    149             try {
    150                 AudioCodec codec = stream.getCodec();
    151                 String codecSpec = String.format(Locale.US, "%d %s %s", codec.type,
    152                         codec.rtpmap, codec.fmtp);
    153                 long id = nativeAdd(stream.getMode(), stream.getSocket(),
    154                         stream.getRemoteAddress().getHostAddress(),
    155                         stream.getRemotePort(), codecSpec, stream.getDtmfType(),
    156                         ActivityThread.currentOpPackageName());
    157                 mStreams.put(stream, id);
    158             } catch (NullPointerException e) {
    159                 throw new IllegalStateException(e);
    160             }
    161         }
    162     }
    163 
    164     private native long nativeAdd(int mode, int socket, String remoteAddress,
    165             int remotePort, String codecSpec, int dtmfType, String opPackageName);
    166 
    167     // Package-private method used by AudioStream.join().
    168     synchronized void remove(AudioStream stream) {
    169         Long id = mStreams.remove(stream);
    170         if (id != null) {
    171             nativeRemove(id);
    172         }
    173     }
    174 
    175     private native void nativeRemove(long id);
    176 
    177     /**
    178      * Sends a DTMF digit to every {@link AudioStream} in this group. Currently
    179      * only event {@code 0} to {@code 15} are supported.
    180      *
    181      * @throws IllegalArgumentException if the event is invalid.
    182      */
    183     public void sendDtmf(int event) {
    184         if (event < 0 || event > 15) {
    185             throw new IllegalArgumentException("Invalid event");
    186         }
    187         synchronized (this) {
    188             nativeSendDtmf(event);
    189         }
    190     }
    191 
    192     private native void nativeSendDtmf(int event);
    193 
    194     /**
    195      * Removes every {@link AudioStream} in this group.
    196      */
    197     public void clear() {
    198         for (AudioStream stream : getStreams()) {
    199             stream.join(null);
    200         }
    201     }
    202 
    203     @Override
    204     protected void finalize() throws Throwable {
    205         nativeRemove(0L);
    206         super.finalize();
    207     }
    208 }
    209