1 /* 2 * Copyright 2020 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 package org.hyphonate.megaaudio.duplex; 17 18 import android.media.AudioDeviceInfo; 19 import android.util.Log; 20 21 import org.hyphonate.megaaudio.common.BuilderBase; 22 import org.hyphonate.megaaudio.common.StreamBase; 23 import org.hyphonate.megaaudio.player.AudioSource; 24 import org.hyphonate.megaaudio.player.AudioSourceProvider; 25 import org.hyphonate.megaaudio.player.Player; 26 import org.hyphonate.megaaudio.player.PlayerBuilder; 27 import org.hyphonate.megaaudio.recorder.AudioSinkProvider; 28 import org.hyphonate.megaaudio.recorder.Recorder; 29 import org.hyphonate.megaaudio.recorder.RecorderBuilder; 30 31 public class DuplexAudioManager { 32 @SuppressWarnings("unused") 33 private static final String TAG = DuplexAudioManager.class.getSimpleName(); 34 @SuppressWarnings("unused") 35 private static final boolean LOG = true; 36 37 // Player 38 //TODO - explain these constants 39 private int mNumPlayerChannels = 2; 40 private int mPlayerChannelMask = 0; 41 42 private int mPlayerSampleRate = 48000; 43 private int mNumPlayerBurstFrames; 44 45 private Player mPlayer; 46 private AudioSourceProvider mSourceProvider; 47 private AudioDeviceInfo mPlayerSelectedDevice; 48 49 // Recorder 50 private int mNumRecorderChannels = 2; 51 private int mRecorderSampleRate = 48000; 52 private int mNumRecorderBufferFrames; 53 54 private Recorder mRecorder; 55 private AudioSinkProvider mSinkProvider; 56 private AudioDeviceInfo mRecorderSelectedDevice; 57 private int mInputPreset = Recorder.INPUT_PRESET_NONE; 58 59 private int mPlayerSharingMode = BuilderBase.SHARING_MODE_SHARED; 60 private int mRecorderSharingMode = BuilderBase.SHARING_MODE_SHARED; 61 DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider)62 public DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) { 63 setSources(sourceProvider, sinkProvider); 64 } 65 66 /** 67 * Specify the source providers for the source and sink. 68 * @param sourceProvider The AudioSourceProvider for the output stream 69 * @param sinkProvider The AudioSinkProvider for the input stream. 70 */ setSources(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider)71 public void setSources(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) { 72 mSourceProvider = sourceProvider; 73 mSinkProvider = sinkProvider; 74 75 mPlayerSampleRate = StreamBase.getSystemSampleRate(); 76 mRecorderSampleRate = StreamBase.getSystemSampleRate(); 77 } 78 79 // 80 // Be careful using these, they will change after setupStreams is called. 81 // getPlayer()82 public Player getPlayer() { 83 return mPlayer; 84 } getRecorder()85 public Recorder getRecorder() { 86 return mRecorder; 87 } 88 setPlayerSampleRate(int sampleRate)89 public void setPlayerSampleRate(int sampleRate) { 90 mPlayerSampleRate = sampleRate; 91 } 92 setRecordererSampleRate(int sampleRate)93 public void setRecordererSampleRate(int sampleRate) { 94 mPlayerSampleRate = sampleRate; 95 } 96 setPlayerRouteDevice(AudioDeviceInfo deviceInfo)97 public void setPlayerRouteDevice(AudioDeviceInfo deviceInfo) { 98 mPlayerSelectedDevice = deviceInfo; 99 } 100 setRecorderRouteDevice(AudioDeviceInfo deviceInfo)101 public void setRecorderRouteDevice(AudioDeviceInfo deviceInfo) { 102 mRecorderSelectedDevice = deviceInfo; 103 } 104 105 /** 106 * Specifies the number of player (index) channels. 107 * @param numChannels The number of index channels for the player. 108 */ setNumPlayerChannels(int numChannels)109 public void setNumPlayerChannels(int numChannels) { 110 mNumPlayerChannels = numChannels; 111 mPlayerChannelMask = 0; 112 } 113 114 /** 115 * Specifies the positional-mask for the player. 116 * @param mask - An AudioFormat position mask. 117 */ setPlayerChannelMask(int mask)118 public void setPlayerChannelMask(int mask) { 119 mPlayerChannelMask = mask; 120 mNumPlayerChannels = 0; 121 } 122 setNumRecorderChannels(int numChannels)123 public void setNumRecorderChannels(int numChannels) { 124 mNumRecorderChannels = numChannels; 125 } setRecorderSampleRate(int sampleRate)126 public void setRecorderSampleRate(int sampleRate) { 127 mRecorderSampleRate = sampleRate; 128 } 129 setPlayerSharingMode(int mode)130 public void setPlayerSharingMode(int mode) { 131 mPlayerSharingMode = mode; 132 } 133 setRecorderSharingMode(int mode)134 public void setRecorderSharingMode(int mode) { 135 mRecorderSharingMode = mode; 136 } 137 getPlayerChannelCount()138 public int getPlayerChannelCount() { 139 return mPlayer != null ? mPlayer.getChannelCount() : -1; 140 } 141 getRecorderChannelCount()142 public int getRecorderChannelCount() { 143 return mRecorder != null ? mRecorder.getChannelCount() : -1; 144 } 145 146 /** 147 * Specifies the input preset to use for the recorder. 148 * @param preset 149 */ setInputPreset(int preset)150 public void setInputPreset(int preset) { 151 mInputPreset = preset; 152 } 153 154 /** 155 * Initializes (but does not start) the player and recorder streams. 156 * @param playerType The API constant for the player 157 * @param recorderType The API constant for the recorder 158 * @return a StreamBase status code specifying the result. 159 */ buildStreams(int playerType, int recorderType)160 public int buildStreams(int playerType, int recorderType) { 161 // Recorder 162 if ((recorderType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) { 163 try { 164 mNumRecorderBufferFrames = StreamBase.getNumBurstFrames(BuilderBase.TYPE_NONE); 165 RecorderBuilder builder = (RecorderBuilder) new RecorderBuilder() 166 .setRecorderType(recorderType) 167 .setAudioSinkProvider(mSinkProvider) 168 .setInputPreset(mInputPreset) 169 .setSharingMode(mRecorderSharingMode) 170 .setRouteDevice(mRecorderSelectedDevice) 171 .setSampleRate(mRecorderSampleRate) 172 .setChannelCount(mNumRecorderChannels) 173 .setNumExchangeFrames(mNumRecorderBufferFrames); 174 mRecorder = builder.build(); 175 } catch (RecorderBuilder.BadStateException ex) { 176 Log.e(TAG, "Recorder - BadStateException" + ex); 177 return StreamBase.ERROR_UNSUPPORTED; 178 } 179 } 180 181 // Player 182 if ((playerType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) { 183 try { 184 mNumPlayerBurstFrames = StreamBase.getNumBurstFrames(playerType); 185 PlayerBuilder builder = (PlayerBuilder) new PlayerBuilder() 186 .setPlayerType(playerType) 187 .setSourceProvider(mSourceProvider) 188 .setSampleRate(mPlayerSampleRate) 189 .setChannelCount(mNumPlayerChannels) 190 .setSharingMode(mPlayerSharingMode) 191 .setRouteDevice(mPlayerSelectedDevice) 192 .setNumExchangeFrames(mNumPlayerBurstFrames) 193 .setPerformanceMode(BuilderBase.PERFORMANCE_MODE_LOWLATENCY); 194 if (mNumPlayerChannels == 0) { 195 builder.setChannelMask(mPlayerChannelMask); 196 } else { 197 builder.setChannelCount(mNumPlayerChannels); 198 } 199 mPlayer = builder.build(); 200 } catch (PlayerBuilder.BadStateException ex) { 201 Log.e(TAG, "Player - BadStateException" + ex); 202 return StreamBase.ERROR_UNSUPPORTED; 203 } catch (Exception ex) { 204 Log.e(TAG, "Uncaught Error in Player Setup for DuplexAudioManager ex:" + ex); 205 } 206 } 207 208 return StreamBase.OK; 209 } 210 start()211 public int start() { 212 if (LOG) { 213 Log.d(TAG, "start()..."); 214 } 215 216 int result = StreamBase.OK; 217 if (mPlayer != null && (result = mPlayer.startStream()) != StreamBase.OK) { 218 if (LOG) { 219 Log.d(TAG, " player fails result:" + result); 220 } 221 return result; 222 } 223 224 if (mRecorder != null && (result = mRecorder.startStream()) != StreamBase.OK) { 225 if (LOG) { 226 Log.d(TAG, " recorder fails result:" + result); 227 } 228 // Shut down 229 stop(); 230 231 return result; 232 } 233 234 if (LOG) { 235 Log.d(TAG, " result:" + result); 236 } 237 return result; 238 } 239 stop()240 public int stop() { 241 if (LOG) { 242 Log.d(TAG, "stop()"); 243 } 244 int playerResult = StreamBase.OK; 245 if (mPlayer != null) { 246 int result1 = mPlayer.stopStream(); 247 int result2 = mPlayer.teardownStream(); 248 playerResult = result1 != StreamBase.OK ? result1 : result2; 249 } 250 251 int recorderResult = StreamBase.OK; 252 if (mRecorder != null) { 253 int result1 = mRecorder.stopStream(); 254 int result2 = mRecorder.teardownStream(); 255 recorderResult = result1 != StreamBase.OK ? result1 : result2; 256 } 257 258 int ret = playerResult != StreamBase.OK ? playerResult : recorderResult; 259 260 if (LOG) { 261 Log.d(TAG, " returns:" + ret); 262 } 263 return ret; 264 } 265 getNumPlayerBufferFrames()266 public int getNumPlayerBufferFrames() { 267 return mPlayer != null ? mPlayer.getSystemBurstFrames() : 0; 268 } 269 getNumRecorderBufferFrames()270 public int getNumRecorderBufferFrames() { 271 return mRecorder != null ? mRecorder.getSystemBurstFrames() : 0; 272 } 273 getAudioSource()274 public AudioSource getAudioSource() { 275 return mPlayer != null ? mPlayer.getAudioSource() : null; 276 } 277 278 /** 279 * Don't call this until the streams are started 280 * @return true if both player and recorder are routed to the devices specified 281 * with setRecorderRouteDevice() and setPlayerRouteDevice(). 282 */ validateRouting()283 public boolean validateRouting() { 284 if (mPlayerSelectedDevice == null && mRecorderSelectedDevice == null) { 285 return true; 286 } 287 288 if (mPlayer == null || !mPlayer.isPlaying() 289 || mRecorder == null || !mRecorder.isRecording()) { 290 return false; 291 } 292 293 if (mPlayerSelectedDevice != null 294 && mPlayer.getRoutedDeviceId() != mPlayerSelectedDevice.getId()) { 295 return false; 296 } 297 298 if (mRecorderSelectedDevice != null 299 && mRecorder.getRoutedDeviceId() != mRecorderSelectedDevice.getId()) { 300 return false; 301 } 302 303 // Everything checks out OK. 304 return true; 305 } 306 307 /** 308 * Don't call this until the streams are started 309 * @return true if the player is using the specified sharing mode set with 310 * setPlayerSharingMode(). 311 */ isSpecifiedPlayerSharingMode()312 public boolean isSpecifiedPlayerSharingMode() { 313 boolean playerOK = false; 314 if (mPlayer != null) { 315 int sharingMode = mPlayer.getSharingMode(); 316 playerOK = sharingMode == mPlayerSharingMode 317 || sharingMode == BuilderBase.SHARING_MODE_NOTSUPPORTED; 318 } 319 return playerOK; 320 } 321 322 /** 323 * Don't call this until the streams are started 324 * @return true if the recorder is using the specified sharing mode set with 325 * setRecorderSharingMode(). 326 */ isSpecifiedRecorderSharingMode()327 public boolean isSpecifiedRecorderSharingMode() { 328 boolean recorderOK = false; 329 if (mRecorder != null) { 330 int sharingMode = mRecorder.getSharingMode(); 331 recorderOK = sharingMode == mRecorderSharingMode 332 || sharingMode == BuilderBase.SHARING_MODE_NOTSUPPORTED; 333 } 334 return recorderOK; 335 } 336 337 /** 338 * Don't call this until the streams are started 339 * @return true if the player is using MMAP. 340 */ isPlayerStreamMMap()341 public boolean isPlayerStreamMMap() { 342 return mPlayer.isMMap(); 343 } 344 345 /** 346 * Don't call this until the streams are started 347 * @return true if the recorders is using MMAP. 348 */ isRecorderStreamMMap()349 public boolean isRecorderStreamMMap() { 350 return mRecorder.isMMap(); 351 } 352 } 353