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 // see Performance Mode Constants in BuilderBase.java 46 private int mPlayerPerformanceMode = BuilderBase.PERFORMANCE_MODE_LOWLATENCY; 47 private int mRecorderPerformanceMode = BuilderBase.PERFORMANCE_MODE_LOWLATENCY; 48 49 private Player mPlayer; 50 private AudioSourceProvider mSourceProvider; 51 private AudioDeviceInfo mPlayerSelectedDevice; 52 53 // Recorder 54 private int mNumRecorderChannels = 2; 55 private int mRecorderSampleRate = 48000; 56 private int mNumRecorderBufferFrames; 57 58 private Recorder mRecorder; 59 private AudioSinkProvider mSinkProvider; 60 private AudioDeviceInfo mRecorderSelectedDevice; 61 private int mInputPreset = Recorder.INPUT_PRESET_NONE; 62 63 private int mPlayerSharingMode = BuilderBase.SHARING_MODE_SHARED; 64 private int mRecorderSharingMode = BuilderBase.SHARING_MODE_SHARED; 65 DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider)66 public DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) { 67 setSources(sourceProvider, sinkProvider); 68 } 69 70 /** 71 * Specify the source providers for the source and sink. 72 * @param sourceProvider The AudioSourceProvider for the output stream 73 * @param sinkProvider The AudioSinkProvider for the input stream. 74 */ setSources(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider)75 public void setSources(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) { 76 mSourceProvider = sourceProvider; 77 mSinkProvider = sinkProvider; 78 79 mPlayerSampleRate = StreamBase.getSystemSampleRate(); 80 mRecorderSampleRate = StreamBase.getSystemSampleRate(); 81 } 82 83 // 84 // Be careful using these, they will change after setupStreams is called. 85 // getPlayer()86 public Player getPlayer() { 87 return mPlayer; 88 } getRecorder()89 public Recorder getRecorder() { 90 return mRecorder; 91 } 92 setPlayerSampleRate(int sampleRate)93 public void setPlayerSampleRate(int sampleRate) { 94 mPlayerSampleRate = sampleRate; 95 } 96 setRecordererSampleRate(int sampleRate)97 public void setRecordererSampleRate(int sampleRate) { 98 mPlayerSampleRate = sampleRate; 99 } 100 setPlayerRouteDevice(AudioDeviceInfo deviceInfo)101 public void setPlayerRouteDevice(AudioDeviceInfo deviceInfo) { 102 mPlayerSelectedDevice = deviceInfo; 103 } 104 setRecorderRouteDevice(AudioDeviceInfo deviceInfo)105 public void setRecorderRouteDevice(AudioDeviceInfo deviceInfo) { 106 mRecorderSelectedDevice = deviceInfo; 107 } 108 109 /** 110 * Specifies the number of player (index) channels. 111 * @param numChannels The number of index channels for the player. 112 */ setNumPlayerChannels(int numChannels)113 public void setNumPlayerChannels(int numChannels) { 114 mNumPlayerChannels = numChannels; 115 mPlayerChannelMask = 0; 116 } 117 118 /** 119 * Specifies the positional-mask for the player. 120 * @param mask - An AudioFormat position mask. 121 */ setPlayerChannelMask(int mask)122 public void setPlayerChannelMask(int mask) { 123 mPlayerChannelMask = mask; 124 mNumPlayerChannels = 0; 125 } 126 setNumRecorderChannels(int numChannels)127 public void setNumRecorderChannels(int numChannels) { 128 mNumRecorderChannels = numChannels; 129 } setRecorderSampleRate(int sampleRate)130 public void setRecorderSampleRate(int sampleRate) { 131 mRecorderSampleRate = sampleRate; 132 } 133 setPlayerSharingMode(int mode)134 public void setPlayerSharingMode(int mode) { 135 mPlayerSharingMode = mode; 136 } 137 setRecorderSharingMode(int mode)138 public void setRecorderSharingMode(int mode) { 139 mRecorderSharingMode = mode; 140 } 141 getPlayerChannelCount()142 public int getPlayerChannelCount() { 143 return mPlayer != null ? mPlayer.getChannelCount() : -1; 144 } 145 getRecorderChannelCount()146 public int getRecorderChannelCount() { 147 return mRecorder != null ? mRecorder.getChannelCount() : -1; 148 } 149 150 /** 151 * Specifies the Performance Mode. 152 */ setPlayerPerformanceMode(int performanceMode)153 public void setPlayerPerformanceMode(int performanceMode) { 154 mPlayerPerformanceMode = performanceMode; 155 } 156 getPlayerPerformanceMode()157 public int getPlayerPerformanceMode() { 158 return mPlayerPerformanceMode; 159 } 160 161 /** 162 * Specifies the Performance Mode. 163 */ setRecorderPerformanceMode(int performanceMode)164 public void setRecorderPerformanceMode(int performanceMode) { 165 mRecorderPerformanceMode = performanceMode; 166 } 167 getRecorderPerformanceMode()168 public int getRecorderPerformanceMode() { 169 return mRecorderPerformanceMode; 170 } 171 172 /** 173 * Specifies the input preset to use for the recorder. 174 * @param preset 175 */ setInputPreset(int preset)176 public void setInputPreset(int preset) { 177 mInputPreset = preset; 178 } 179 180 // Which component of the duplex has the error 181 public static final int DUPLEX_STREAM_ID = 0x00030000; 182 public static final int DUPLEX_RECORDER = 0x00010000; 183 public static final int DUPLEX_PLAYER = 0x00020000; 184 185 // Which part of the process has the error 186 public static final int DUPLEX_ERROR_CODE = 0xFFFC0000; 187 public static final int DUPLEX_ERROR_NONE = 0x00000000; 188 public static final int DUPLEX_ERR_BUILD = 0x00040000; 189 public static final int DUPLEX_ERR_OPEN = 0x00080000; 190 public static final int DUPLEX_ERR_START = 0x00100000; 191 192 // The MegaAudio error (success) code 193 public static final int DUPLEX_MEGAAUDIO_CODE = 0x0000FFFF; 194 195 // Handy status code for success building/opening both paths 196 public static final int DUPLEX_SUCCESS = DUPLEX_RECORDER | DUPLEX_PLAYER | StreamBase.OK; 197 198 /** 199 * Initializes (but does not start) the player and recorder streams. 200 * @param playerType The API constant for the player 201 * @param recorderType The API constant for the recorder 202 * @return A set of the above constants indicating success/failure, for which stream, 203 * what part of the process and the MegaAudio error code in the bottom 16-bits 204 */ buildStreams(int playerType, int recorderType)205 public int buildStreams(int playerType, int recorderType) { 206 if (LOG) { 207 Log.d(TAG, "buildStreams()"); 208 } 209 // Recorder 210 if ((recorderType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) { 211 int buildResult = StreamBase.ERROR_UNKNOWN; 212 int openResult = StreamBase.ERROR_UNKNOWN; 213 try { 214 Log.d(TAG, " Recorder..."); 215 mNumRecorderBufferFrames = StreamBase.getNumBurstFrames(BuilderBase.TYPE_NONE); 216 RecorderBuilder builder = (RecorderBuilder) new RecorderBuilder() 217 .setRecorderType(recorderType) 218 .setAudioSinkProvider(mSinkProvider) 219 .setInputPreset(mInputPreset) 220 .setSharingMode(mRecorderSharingMode) 221 .setRouteDevice(mRecorderSelectedDevice) 222 .setSampleRate(mRecorderSampleRate) 223 .setChannelCount(mNumRecorderChannels) 224 .setNumExchangeFrames(mNumRecorderBufferFrames) 225 .setPerformanceMode(mRecorderPerformanceMode); 226 mRecorder = builder.allocStream(); 227 if ((buildResult = mRecorder.build(builder)) == StreamBase.OK 228 && (openResult = mRecorder.open()) == StreamBase.OK) { 229 Log.d(TAG, " Recorder - Success!"); 230 } else { 231 Log.d(TAG, " Recorder - buildResult:" + buildResult 232 + " openResult:" + openResult); 233 if (buildResult != StreamBase.OK) { 234 // build() failed 235 return DUPLEX_RECORDER | DUPLEX_ERR_BUILD | buildResult; 236 } else { 237 // open() failed 238 return DUPLEX_RECORDER | DUPLEX_ERR_OPEN | buildResult; 239 } 240 } 241 } catch (RecorderBuilder.BadStateException ex) { 242 Log.e(TAG, "Recorder - BadStateException" + ex); 243 return ex.getStatusCode(); 244 } catch (Exception ex) { 245 Log.e(TAG, "Unexpected Error in Recorder Setup for DuplexAudioManager ex:" + ex); 246 return StreamBase.ERROR_UNKNOWN; 247 } 248 } 249 250 // Player 251 if ((playerType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) { 252 Log.d(TAG, " Player..."); 253 try { 254 int buildResult = StreamBase.ERROR_UNKNOWN; 255 int openResult = StreamBase.ERROR_UNKNOWN; 256 mNumPlayerBurstFrames = StreamBase.getNumBurstFrames(playerType); 257 PlayerBuilder builder = (PlayerBuilder) new PlayerBuilder() 258 .setPlayerType(playerType) 259 .setSourceProvider(mSourceProvider) 260 .setSampleRate(mPlayerSampleRate) 261 .setChannelCount(mNumPlayerChannels) 262 .setSharingMode(mPlayerSharingMode) 263 .setRouteDevice(mPlayerSelectedDevice) 264 .setNumExchangeFrames(mNumPlayerBurstFrames) 265 .setPerformanceMode(mPlayerPerformanceMode); 266 if (mNumPlayerChannels == 0) { 267 builder.setChannelMask(mPlayerChannelMask); 268 } else { 269 builder.setChannelCount(mNumPlayerChannels); 270 } 271 mPlayer = builder.allocStream(); 272 if ((buildResult = mPlayer.build(builder)) == StreamBase.OK 273 && (openResult = mPlayer.open()) == StreamBase.OK) { 274 Log.d(TAG, " Player - Success!"); 275 } else { 276 Log.d(TAG, " Player - buildResult:" + buildResult 277 + " openResult:" + openResult); 278 if (buildResult != StreamBase.OK) { 279 // build() failed 280 return DUPLEX_PLAYER | DUPLEX_ERR_BUILD | buildResult; 281 } else { 282 // open() failed 283 return DUPLEX_PLAYER | DUPLEX_ERR_OPEN | buildResult; 284 } 285 } 286 } catch (PlayerBuilder.BadStateException ex) { 287 Log.e(TAG, "Player - BadStateException" + ex); 288 return ex.getStatusCode(); 289 } catch (Exception ex) { 290 Log.e(TAG, "Unexpected Error in Player Setup for DuplexAudioManager ex:" + ex); 291 return StreamBase.ERROR_UNKNOWN; 292 } 293 } 294 295 return DUPLEX_RECORDER | DUPLEX_PLAYER | StreamBase.OK; 296 } 297 start()298 public int start() { 299 if (LOG) { 300 Log.d(TAG, "start()..."); 301 } 302 303 int result = StreamBase.OK; 304 if (mPlayer != null && (result = mPlayer.start()) != StreamBase.OK) { 305 if (LOG) { 306 Log.d(TAG, " player fails result:" + result); 307 } 308 return DUPLEX_PLAYER | DUPLEX_ERR_START | result; 309 } 310 311 if (mRecorder != null && (result = mRecorder.start()) != StreamBase.OK) { 312 if (LOG) { 313 Log.d(TAG, " recorder fails result:" + result); 314 } 315 316 return DUPLEX_RECORDER | DUPLEX_ERR_START | result; 317 } 318 319 if (LOG) { 320 Log.d(TAG, " result:" + result); 321 } 322 return DUPLEX_PLAYER | DUPLEX_RECORDER | DUPLEX_ERROR_NONE | result; 323 } 324 325 326 /** 327 * Stops and tearsdown both streams. 328 * It's not clear we can return a useful error code, so just let StreamBase.unwind() 329 * do the work. 330 */ stop()331 public void stop() { 332 if (LOG) { 333 Log.d(TAG, "stop()"); 334 } 335 unwind(); 336 } 337 338 /** 339 * Unwinds both Player and Recorder (as appropriate) 340 */ unwind()341 public void unwind() { 342 if (mPlayer != null) { 343 mPlayer.unwind(); 344 mPlayer = null; 345 } 346 if (mRecorder != null) { 347 mRecorder.unwind(); 348 mRecorder = null; 349 } 350 } 351 getNumPlayerBufferFrames()352 public int getNumPlayerBufferFrames() { 353 return mPlayer != null ? mPlayer.getSystemBurstFrames() : 0; 354 } 355 getNumRecorderBufferFrames()356 public int getNumRecorderBufferFrames() { 357 return mRecorder != null ? mRecorder.getSystemBurstFrames() : 0; 358 } 359 getAudioSource()360 public AudioSource getAudioSource() { 361 return mPlayer != null ? mPlayer.getAudioSource() : null; 362 } 363 364 /** 365 * Don't call this until the streams are started 366 * @return true if both player and recorder are routed to the devices specified 367 * with setRecorderRouteDevice() and setPlayerRouteDevice(). 368 */ validateRouting()369 public boolean validateRouting() { 370 if (mPlayerSelectedDevice == null && mRecorderSelectedDevice == null) { 371 return true; 372 } 373 374 if (mPlayer == null || !mPlayer.isPlaying() 375 || mRecorder == null || !mRecorder.isRecording()) { 376 return false; 377 } 378 379 if (mPlayerSelectedDevice != null 380 && mPlayer.getRoutedDeviceId() != mPlayerSelectedDevice.getId()) { 381 return false; 382 } 383 384 if (mRecorderSelectedDevice != null 385 && mRecorder.getRoutedDeviceId() != mRecorderSelectedDevice.getId()) { 386 return false; 387 } 388 389 // Everything checks out OK. 390 return true; 391 } 392 393 /** 394 * Don't call this until the streams are started 395 * @return true if the player is using the specified sharing mode set with 396 * setPlayerSharingMode(). 397 */ isSpecifiedPlayerSharingMode()398 public boolean isSpecifiedPlayerSharingMode() { 399 boolean playerOK = false; 400 if (mPlayer != null) { 401 int sharingMode = mPlayer.getSharingMode(); 402 playerOK = sharingMode == mPlayerSharingMode 403 || sharingMode == BuilderBase.SHARING_MODE_NOTSUPPORTED; 404 } 405 return playerOK; 406 } 407 408 /** 409 * Don't call this until the streams are started 410 * @return true if the recorder is using the specified sharing mode set with 411 * setRecorderSharingMode(). 412 */ isSpecifiedRecorderSharingMode()413 public boolean isSpecifiedRecorderSharingMode() { 414 boolean recorderOK = false; 415 if (mRecorder != null) { 416 int sharingMode = mRecorder.getSharingMode(); 417 recorderOK = sharingMode == mRecorderSharingMode 418 || sharingMode == BuilderBase.SHARING_MODE_NOTSUPPORTED; 419 } 420 return recorderOK; 421 } 422 423 /** 424 * Don't call this until the streams are started 425 * @return true if the player is using MMAP. 426 */ isPlayerStreamMMap()427 public boolean isPlayerStreamMMap() { 428 return mPlayer.isMMap(); 429 } 430 431 /** 432 * Don't call this until the streams are started 433 * @return true if the recorders is using MMAP. 434 */ isRecorderStreamMMap()435 public boolean isRecorderStreamMMap() { 436 return mRecorder.isMMap(); 437 } 438 } 439