• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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