• 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.player;
17 
18 import android.media.AudioDeviceInfo;
19 import android.media.AudioFormat;
20 import android.media.AudioTimestamp;
21 import android.media.AudioTrack;
22 import android.util.Log;
23 
24 import org.hyphonate.megaaudio.common.BuilderBase;
25 import org.hyphonate.megaaudio.common.StreamBase;
26 import org.hyphonate.megaaudio.common.StreamState;
27 
28 /**
29  * Implementation of abstract Player class implemented for the Android Java-based audio playback
30  * API, i.e. AudioTrack.
31  */
32 public class JavaPlayer extends Player {
33     @SuppressWarnings("unused")
34     private static final String TAG = JavaPlayer.class.getSimpleName();
35     @SuppressWarnings("unused")
36     private static final boolean LOG = true;
37 
38     /*
39      * Player infrastructure
40      */
41     /* The AudioTrack for playing the audio stream */
42     private AudioTrack mAudioTrack;
43 
44     /*
45      * Data buffers
46      */
47     /** The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack. */
48     private float[] mAudioBuffer;
49 
50     // Player-specific extension
51 
52     /**
53      * @return The underlying Java API AudioTrack object
54      */
getAudioTrack()55     public AudioTrack getAudioTrack() { return mAudioTrack; }
56 
57     /**
58      * Constructs a JavaPlayer object. Create and sets up the AudioTrack for playback.
59      * @param builder   Provides the attributes for the underlying AudioTrack.
60      * @param sourceProvider The AudioSource object providing audio data to play.
61      */
JavaPlayer(PlayerBuilder builder, AudioSourceProvider sourceProvider)62     public JavaPlayer(PlayerBuilder builder, AudioSourceProvider sourceProvider) {
63         super(sourceProvider);
64         mNumExchangeFrames = -1;   // TODO need error defines
65 
66         setupStream(builder);
67     }
68 
69     /**
70      * Allocates the array for the burst buffer.
71      */
allocBurstBuffer()72     private void allocBurstBuffer() {
73         if (LOG) {
74             Log.i(TAG, "allocBurstBuffer() mNumExchangeFrames:" + mNumExchangeFrames);
75         }
76         // pad it by 1 frame. This allows some sources to not have to worry about
77         // handling the end-of-buffer edge case. i.e. a "Guard Point" for interpolation.
78         mAudioBuffer = new float[(mNumExchangeFrames + 1) * mChannelCount];
79     }
80 
81     //
82     // Attributes
83     //
84     @Override
getRoutedDeviceId()85     public int getRoutedDeviceId() {
86         if (mAudioTrack != null) {
87             AudioDeviceInfo routedDevice = mAudioTrack.getRoutedDevice();
88             return routedDevice != null
89                     ? routedDevice.getId() : BuilderBase.ROUTED_DEVICE_ID_DEFAULT;
90         } else {
91             return BuilderBase.ROUTED_DEVICE_ID_DEFAULT;
92         }
93     }
94 
95     /*
96      * State
97      */
setupStream(PlayerBuilder builder)98     private int setupStream(PlayerBuilder builder) {
99         mChannelCount = builder.getChannelCount();
100         mSampleRate = builder.getSampleRate();
101         mNumExchangeFrames = builder.getNumExchangeFrames();
102         mPerformanceMode = builder.getJavaPerformanceMode();
103         int routeDeviceId = builder.getRouteDeviceId();
104         if (LOG) {
105             Log.i(TAG, "setupStream()");
106             Log.i(TAG, "  chans:" + mChannelCount);
107             Log.i(TAG, "  rate: " + mSampleRate);
108             Log.i(TAG, "  frames: " + mNumExchangeFrames);
109             Log.i(TAG, "  perf mode: " + mPerformanceMode);
110             Log.i(TAG, "  route device: " + routeDeviceId);
111         }
112 
113         mAudioSource = mSourceProvider.getJavaSource();
114         mAudioSource.init(mNumExchangeFrames, mChannelCount);
115 
116         try {
117             AudioFormat.Builder formatBuilder = new AudioFormat.Builder();
118             formatBuilder.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
119                 .setSampleRate(mSampleRate)
120                 // setChannelIndexMask() won't give us a FAST_PATH
121                 // .setChannelIndexMask(
122                 //      StreamBase.channelCountToIndexMask(mChannelCount))
123                 .setChannelMask(StreamBase.channelCountToOutPositionMask(mChannelCount));
124             AudioTrack.Builder audioTrackBuilder = new AudioTrack.Builder();
125             audioTrackBuilder.setAudioFormat(formatBuilder.build())
126                 .setPerformanceMode(mPerformanceMode);
127             mAudioTrack = audioTrackBuilder.build();
128 
129             allocBurstBuffer();
130             mAudioTrack.setPreferredDevice(builder.getRouteDevice());
131 
132             if (LOG) {
133                 Log.i(TAG, "  mAudioTrack.getBufferSizeInFrames(): "
134                         + mAudioTrack.getBufferSizeInFrames());
135                 Log.i(TAG, "  mAudioTrack.getBufferCapacityInFrames() :"
136                         + mAudioTrack.getBufferCapacityInFrames());
137             }
138         }  catch (UnsupportedOperationException ex) {
139             Log.e(TAG, "Couldn't open AudioTrack: " + ex);
140             return ERROR_UNSUPPORTED;
141         } catch (java.lang.IllegalArgumentException ex) {
142             Log.e(TAG, "Invalid arguments to AudioTrack.Builder: " + ex);
143             return ERROR_UNSUPPORTED;
144         }
145 
146         return OK;
147     }
148 
149     @Override
teardownStream()150     public int teardownStream() {
151         if (LOG) {
152             Log.i(TAG, "teardownStream()");
153         }
154         stopStream();
155 
156         waitForStreamThreadToExit();
157 
158         if (mAudioTrack != null) {
159             mAudioTrack.release();
160             mAudioTrack = null;
161         }
162 
163         mChannelCount = 0;
164         mSampleRate = 0;
165 
166         //TODO - Retrieve errors from above
167         return OK;
168     }
169 
170     /**
171      * Allocates the underlying AudioTrack and begins Playback.
172      * @return True if the stream is successfully started.
173      *
174      * This method returns when the start operation is complete, but before the first
175      * call to the AudioSource.pull() method.
176      */
177     @Override
startStream()178     public int startStream() {
179         if (mAudioTrack == null) {
180             return ERROR_INVALID_STATE;
181         }
182         waitForStreamThreadToExit(); // just to be sure.
183 
184         mStreamThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
185         mPlaying = true;
186         mStreamThread.start();
187 
188         return OK;
189     }
190 
191     /**
192      * Marks the stream for stopping on the next callback from the underlying system.
193      *
194      * Returns immediately, though a call to AudioSource.pull() may be in progress.
195      */
196     @Override
stopStream()197     public int stopStream() {
198         mPlaying = false;
199         return OK;
200     }
201 
202     /**
203      * @return See StreamState constants
204      */
getStreamState()205     public int getStreamState() {
206         //TODO - track state so we can return something meaningful here.
207         return StreamState.UNKNOWN;
208     }
209 
210     /**
211      * @return The last error callback result (these must match Oboe). See Oboe constants
212      */
getLastErrorCallbackResult()213     public int getLastErrorCallbackResult() {
214         //TODO - track errors so we can return something meaningful here.
215         return ERROR_UNKNOWN;
216     }
217 
218     /**
219      * Gets a timestamp from the audio stream
220      * @param timestamp
221      * @return
222      */
getTimestamp(AudioTimestamp timestamp)223     public boolean getTimestamp(AudioTimestamp timestamp) {
224         return mPlaying ? mAudioTrack.getTimestamp(timestamp) : false;
225     }
226 
227     //
228     // StreamPlayerRunnable
229     //
230     /**
231      * Implements the <code>run</code> method for the playback thread.
232      * Gets initial audio data and starts the AudioTrack. Then continuously provides audio data
233      * until the flag <code>mPlaying</code> is set to false (in the stop() method).
234      */
235     private class StreamPlayerRunnable implements Runnable {
236         @Override
run()237         public void run() {
238             final int mNumPlaySamples = mNumExchangeFrames * mChannelCount;
239             if (LOG) {
240                 Log.i(TAG, "mNumPlaySamples: " + mNumPlaySamples);
241             }
242             mAudioTrack.play();
243             while (mPlaying) {
244                 mAudioSource.pull(mAudioBuffer, mNumExchangeFrames, mChannelCount);
245 
246                 onPull();
247 
248                 int numSamplesWritten = mAudioTrack.write(
249                         mAudioBuffer, 0, mNumPlaySamples, AudioTrack.WRITE_BLOCKING);
250                 if (numSamplesWritten < 0) {
251                     // error
252                     Log.e(TAG, "AudioTrack write error - numSamplesWritten: " + numSamplesWritten);
253                     stopStream();
254                 } else if (numSamplesWritten < mNumPlaySamples) {
255                     // end of stream
256                     if (LOG) {
257                         Log.i(TAG, "Stream Complete.");
258                     }
259                     stopStream();
260                 }
261             }
262         }
263     }
264 }
265