Home | History | Annotate | Download | only in audio
      1 /*
      2  * Copyright (C) 2016 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 com.android.server.audio;
     18 
     19 import android.media.AudioFormat;
     20 import android.media.AudioManager;
     21 import android.media.AudioRecordingConfiguration;
     22 import android.media.AudioSystem;
     23 import android.media.IRecordingConfigDispatcher;
     24 import android.media.MediaRecorder;
     25 import android.os.IBinder;
     26 import android.os.RemoteException;
     27 import android.util.Log;
     28 
     29 import java.util.ArrayList;
     30 import java.util.HashMap;
     31 import java.util.Iterator;
     32 import java.util.List;
     33 
     34 /**
     35  * Class to receive and dispatch updates from AudioSystem about recording configurations.
     36  */
     37 public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
     38 
     39     public final static String TAG = "AudioService.RecordingActivityMonitor";
     40 
     41     private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
     42 
     43     private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
     44             new HashMap<Integer, AudioRecordingConfiguration>();
     45 
     46     RecordingActivityMonitor() {
     47         RecMonitorClient.sMonitor = this;
     48     }
     49 
     50     /**
     51      * Implementation of android.media.AudioSystem.AudioRecordingCallback
     52      */
     53     public void onRecordingConfigurationChanged(int event, int session, int source,
     54             int[] recordingInfo) {
     55         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
     56             return;
     57         }
     58         final List<AudioRecordingConfiguration> configs =
     59                 updateSnapshot(event, session, source, recordingInfo);
     60         if (configs != null){
     61             synchronized(mClients) {
     62                 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
     63                 while (clientIterator.hasNext()) {
     64                     try {
     65                         clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange(
     66                                 configs);
     67                     } catch (RemoteException e) {
     68                         Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
     69                     }
     70                 }
     71             }
     72         }
     73     }
     74 
     75     void initMonitor() {
     76         AudioSystem.setRecordingCallback(this);
     77     }
     78 
     79     void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
     80         if (rcdb == null) {
     81             return;
     82         }
     83         synchronized(mClients) {
     84             final RecMonitorClient rmc = new RecMonitorClient(rcdb);
     85             if (rmc.init()) {
     86                 mClients.add(rmc);
     87             }
     88         }
     89     }
     90 
     91     void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
     92         if (rcdb == null) {
     93             return;
     94         }
     95         synchronized(mClients) {
     96             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
     97             while (clientIterator.hasNext()) {
     98                 RecMonitorClient rmc = clientIterator.next();
     99                 if (rcdb.equals(rmc.mDispatcherCb)) {
    100                     rmc.release();
    101                     clientIterator.remove();
    102                     break;
    103                 }
    104             }
    105         }
    106     }
    107 
    108     List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
    109         synchronized(mRecordConfigs) {
    110             return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
    111         }
    112     }
    113 
    114     /**
    115      * Update the internal "view" of the active recording sessions
    116      * @param event
    117      * @param session
    118      * @param source
    119      * @param recordingFormat see
    120      *     {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])}
    121      *     for the definition of the contents of the array
    122      * @return null if the list of active recording sessions has not been modified, a list
    123      *     with the current active configurations otherwise.
    124      */
    125     private List<AudioRecordingConfiguration> updateSnapshot(int event, int session, int source,
    126             int[] recordingInfo) {
    127         final boolean configChanged;
    128         final ArrayList<AudioRecordingConfiguration> configs;
    129         synchronized(mRecordConfigs) {
    130             switch (event) {
    131             case AudioManager.RECORD_CONFIG_EVENT_STOP:
    132                 // return failure if an unknown recording session stopped
    133                 configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
    134                 break;
    135             case AudioManager.RECORD_CONFIG_EVENT_START:
    136                 final AudioFormat clientFormat = new AudioFormat.Builder()
    137                         .setEncoding(recordingInfo[0])
    138                         // FIXME this doesn't support index-based masks
    139                         .setChannelMask(recordingInfo[1])
    140                         .setSampleRate(recordingInfo[2])
    141                         .build();
    142                 final AudioFormat deviceFormat = new AudioFormat.Builder()
    143                         .setEncoding(recordingInfo[3])
    144                         // FIXME this doesn't support index-based masks
    145                         .setChannelMask(recordingInfo[4])
    146                         .setSampleRate(recordingInfo[5])
    147                         .build();
    148                 final int patchHandle = recordingInfo[6];
    149                 final Integer sessionKey = new Integer(session);
    150                 if (mRecordConfigs.containsKey(sessionKey)) {
    151                     final AudioRecordingConfiguration updatedConfig =
    152                             new AudioRecordingConfiguration(session, source,
    153                                     clientFormat, deviceFormat, patchHandle);
    154                     if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
    155                         configChanged = false;
    156                     } else {
    157                         // config exists but has been modified
    158                         mRecordConfigs.remove(sessionKey);
    159                         mRecordConfigs.put(sessionKey, updatedConfig);
    160                         configChanged = true;
    161                     }
    162                 } else {
    163                     mRecordConfigs.put(sessionKey,
    164                             new AudioRecordingConfiguration(session, source,
    165                                     clientFormat, deviceFormat, patchHandle));
    166                     configChanged = true;
    167                 }
    168                 break;
    169             default:
    170                 Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
    171                         event, session, source));
    172                 configChanged = false;
    173             }
    174             if (configChanged) {
    175                 configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
    176             } else {
    177                 configs = null;
    178             }
    179         }
    180         return configs;
    181     }
    182 
    183     /**
    184      * Inner class to track clients that want to be notified of recording updates
    185      */
    186     private final static class RecMonitorClient implements IBinder.DeathRecipient {
    187 
    188         // can afford to be static because only one RecordingActivityMonitor ever instantiated
    189         static RecordingActivityMonitor sMonitor;
    190 
    191         final IRecordingConfigDispatcher mDispatcherCb;
    192 
    193         RecMonitorClient(IRecordingConfigDispatcher rcdb) {
    194             mDispatcherCb = rcdb;
    195         }
    196 
    197         public void binderDied() {
    198             Log.w(TAG, "client died");
    199             sMonitor.unregisterRecordingCallback(mDispatcherCb);
    200         }
    201 
    202         boolean init() {
    203             try {
    204                 mDispatcherCb.asBinder().linkToDeath(this, 0);
    205                 return true;
    206             } catch (RemoteException e) {
    207                 Log.w(TAG, "Could not link to client death", e);
    208                 return false;
    209             }
    210         }
    211 
    212         void release() {
    213             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
    214         }
    215     }
    216 }
    217