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