• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 android.media.cts;
17 
18 import android.media.AudioAttributes;
19 import android.media.AudioFormat;
20 import android.media.AudioManager;
21 import android.media.AudioTimestamp;
22 import android.media.AudioTrack;
23 
24 import java.nio.ByteBuffer;
25 import java.util.LinkedList;
26 import java.util.concurrent.atomic.AtomicBoolean;
27 import java.util.concurrent.atomic.AtomicInteger;
28 import java.util.concurrent.atomic.AtomicLong;
29 
30 /**
31  * Class for playing audio by using audio track.
32  * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods will
33  * block until all data has been written to system. In order to avoid blocking, this class
34  * caculates available buffer size first then writes to audio sink.
35  */
36 public class NonBlockingAudioTrack {
37     private static final String TAG = NonBlockingAudioTrack.class.getSimpleName();
38 
39     class QueueElement {
40         ByteBuffer data;
41         int size;
42         long pts;
43     }
44 
45     private AudioTrack mAudioTrack;
46     private int mSampleRate;
47     private int mNumBytesQueued = 0;
48     private AtomicInteger mTotalBytesWritten = new AtomicInteger(0);
49     private LinkedList<QueueElement> mQueue = new LinkedList<QueueElement>();
50     private boolean mStopped;
51     private int mBufferSizeInBytes;
52     private AtomicBoolean mStopWriting = new AtomicBoolean(false);
53 
54     /**
55      * An offset (in nanoseconds) to add to presentation timestamps fed to the {@link AudioTrack}.
56      * This is used to simulate desynchronization between tracks.
57      */
58     private AtomicLong mAudioOffsetNs = new AtomicLong(0);
59 
NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync, int audioSessionId)60     public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
61                     int audioSessionId) {
62         int channelConfig;
63         switch (channelCount) {
64             case 1:
65                 channelConfig = AudioFormat.CHANNEL_OUT_MONO;
66                 break;
67             case 2:
68                 channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
69                 break;
70             case 6:
71                 channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
72                 break;
73             default:
74                 throw new IllegalArgumentException();
75         }
76 
77         int minBufferSize =
78             AudioTrack.getMinBufferSize(
79                     sampleRate,
80                     channelConfig,
81                     AudioFormat.ENCODING_PCM_16BIT);
82 
83         mBufferSizeInBytes = 2 * minBufferSize;
84 
85         if (!hwAvSync) {
86             mAudioTrack = new AudioTrack(
87                     AudioManager.STREAM_MUSIC,
88                     sampleRate,
89                     channelConfig,
90                     AudioFormat.ENCODING_PCM_16BIT,
91                     mBufferSizeInBytes,
92                     AudioTrack.MODE_STREAM);
93         }
94         else {
95             // build AudioTrack using Audio Attributes and FLAG_HW_AV_SYNC
96             AudioAttributes audioAttributes = (new AudioAttributes.Builder())
97                             .setLegacyStreamType(AudioManager.STREAM_MUSIC)
98                             .setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
99                             .build();
100             AudioFormat audioFormat = (new AudioFormat.Builder())
101                             .setChannelMask(channelConfig)
102                             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
103                             .setSampleRate(sampleRate)
104                             .build();
105             mAudioTrack = new AudioTrack(audioAttributes, audioFormat, mBufferSizeInBytes,
106                                     AudioTrack.MODE_STREAM, audioSessionId);
107         }
108 
109         mSampleRate = sampleRate;
110     }
111 
getAudioTimeUs()112     public long getAudioTimeUs() {
113         int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
114 
115         return (numFramesPlayed * 1000000L) / mSampleRate;
116     }
117 
getTimestamp()118     public AudioTimestamp getTimestamp() {
119         AudioTimestamp timestamp = new AudioTimestamp();
120         mAudioTrack.getTimestamp(timestamp);
121         return timestamp;
122     }
123 
getNumBytesQueued()124     public int getNumBytesQueued() {
125         return mNumBytesQueued;
126     }
127 
play()128     public void play() {
129         mStopped = false;
130         mAudioTrack.play();
131     }
132 
stop()133     public void stop() {
134         if (mQueue.isEmpty()) {
135             mAudioTrack.stop();
136             mNumBytesQueued = 0;
137         } else {
138             mStopped = true;
139         }
140     }
141 
setStopWriting(boolean stop)142     public void setStopWriting(boolean stop) {
143         mStopWriting.set(stop);
144     }
145 
setAudioOffsetNs(long audioOffsetNs)146     public void setAudioOffsetNs(long audioOffsetNs) {
147         mAudioOffsetNs.set(audioOffsetNs);
148     }
149 
pause()150     public void pause() {
151         mAudioTrack.pause();
152     }
153 
flush()154     public void flush() {
155         if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
156             return;
157         }
158         mAudioTrack.flush();
159         mQueue.clear();
160         mNumBytesQueued = 0;
161         mStopped = false;
162     }
163 
release()164     public void release() {
165         mQueue.clear();
166         mNumBytesQueued = 0;
167         mAudioTrack.release();
168         mAudioTrack = null;
169         mStopped = false;
170     }
171 
process()172     public void process() {
173         while (!mQueue.isEmpty()) {
174             QueueElement element = mQueue.peekFirst();
175             if (mStopWriting.get()) {
176                 break;
177             }
178 
179             int written = mAudioTrack.write(element.data, element.size,
180                     AudioTrack.WRITE_NON_BLOCKING, element.pts + mAudioOffsetNs.get());
181             if (written < 0) {
182                 throw new RuntimeException("Audiotrack.write() failed.");
183             }
184 
185             mTotalBytesWritten.addAndGet(written);
186             mNumBytesQueued -= written;
187             element.size -= written;
188             if (element.size != 0) {
189                 break;
190             }
191             mQueue.removeFirst();
192         }
193         if (mStopped) {
194             mAudioTrack.stop();
195             mNumBytesQueued = 0;
196             mStopped = false;
197         }
198     }
199 
getFramesWritten()200     public int getFramesWritten() {
201         if (mAudioTrack == null) {
202             return -1;
203         }
204         return mTotalBytesWritten.get() / mAudioTrack.getFormat().getFrameSizeInBytes();
205     }
206 
getPlayState()207     public int getPlayState() {
208         return mAudioTrack.getPlayState();
209     }
210 
write(ByteBuffer data, int size, long pts)211     public void write(ByteBuffer data, int size, long pts) {
212         QueueElement element = new QueueElement();
213         element.data = data;
214         element.size = size;
215         element.pts  = pts;
216 
217         // accumulate size written to queue
218         mNumBytesQueued += size;
219         mQueue.add(element);
220     }
221 
222     /** Returns the underlying {@code AudioTrack}. */
getAudioTrack()223     public AudioTrack getAudioTrack() {
224         return mAudioTrack;
225     }
226 }
227