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.AudioFormat;
19 import android.media.AudioManager;
20 import android.media.AudioTrack;
21 import android.media.AudioAttributes;
22 import android.util.Log;
23 
24 import java.nio.ByteBuffer;
25 import java.util.LinkedList;
26 
27 /**
28  * Class for playing audio by using audio track.
29  * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods will
30  * block until all data has been written to system. In order to avoid blocking, this class
31  * caculates available buffer size first then writes to audio sink.
32  */
33 public class NonBlockingAudioTrack {
34     private static final String TAG = NonBlockingAudioTrack.class.getSimpleName();
35 
36     class QueueElement {
37         ByteBuffer data;
38         int size;
39         long pts;
40     }
41 
42     private AudioTrack mAudioTrack;
43     private int mSampleRate;
44     private int mNumBytesQueued = 0;
45     private LinkedList<QueueElement> mQueue = new LinkedList<QueueElement>();
46     private boolean mStopped;
47 
NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync, int audioSessionId)48     public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
49                     int audioSessionId) {
50         int channelConfig;
51         switch (channelCount) {
52             case 1:
53                 channelConfig = AudioFormat.CHANNEL_OUT_MONO;
54                 break;
55             case 2:
56                 channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
57                 break;
58             case 6:
59                 channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
60                 break;
61             default:
62                 throw new IllegalArgumentException();
63         }
64 
65         int minBufferSize =
66             AudioTrack.getMinBufferSize(
67                     sampleRate,
68                     channelConfig,
69                     AudioFormat.ENCODING_PCM_16BIT);
70 
71         int bufferSize = 2 * minBufferSize;
72 
73         if (!hwAvSync) {
74             mAudioTrack = new AudioTrack(
75                     AudioManager.STREAM_MUSIC,
76                     sampleRate,
77                     channelConfig,
78                     AudioFormat.ENCODING_PCM_16BIT,
79                     bufferSize,
80                     AudioTrack.MODE_STREAM);
81         }
82         else {
83             // build AudioTrack using Audio Attributes and FLAG_HW_AV_SYNC
84             AudioAttributes audioAttributes = (new AudioAttributes.Builder())
85                             .setLegacyStreamType(AudioManager.STREAM_MUSIC)
86                             .setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
87                             .build();
88             AudioFormat audioFormat = (new AudioFormat.Builder())
89                             .setChannelMask(channelConfig)
90                             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
91                             .setSampleRate(sampleRate)
92                             .build();
93              mAudioTrack = new AudioTrack(audioAttributes, audioFormat, bufferSize,
94                                     AudioTrack.MODE_STREAM, audioSessionId);
95         }
96 
97         mSampleRate = sampleRate;
98     }
99 
getAudioTimeUs()100     public long getAudioTimeUs() {
101         int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
102 
103         return (numFramesPlayed * 1000000L) / mSampleRate;
104     }
105 
getNumBytesQueued()106     public int getNumBytesQueued() {
107         return mNumBytesQueued;
108     }
109 
play()110     public void play() {
111         mStopped = false;
112         mAudioTrack.play();
113     }
114 
stop()115     public void stop() {
116         if (mQueue.isEmpty()) {
117             mAudioTrack.stop();
118             mNumBytesQueued = 0;
119         } else {
120             mStopped = true;
121         }
122     }
123 
pause()124     public void pause() {
125         mAudioTrack.pause();
126     }
127 
flush()128     public void flush() {
129         if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
130             return;
131         }
132         mAudioTrack.flush();
133         mQueue.clear();
134         mNumBytesQueued = 0;
135         mStopped = false;
136     }
137 
release()138     public void release() {
139         mQueue.clear();
140         mNumBytesQueued = 0;
141         mAudioTrack.release();
142         mAudioTrack = null;
143         mStopped = false;
144     }
145 
process()146     public void process() {
147         while (!mQueue.isEmpty()) {
148             QueueElement element = mQueue.peekFirst();
149             int written = mAudioTrack.write(element.data, element.size,
150                                             AudioTrack.WRITE_NON_BLOCKING, element.pts);
151             if (written < 0) {
152                 throw new RuntimeException("Audiotrack.write() failed.");
153             }
154 
155             mNumBytesQueued -= written;
156             element.size -= written;
157             if (element.size != 0) {
158                 break;
159             }
160             mQueue.removeFirst();
161         }
162         if (mStopped) {
163             mAudioTrack.stop();
164             mNumBytesQueued = 0;
165             mStopped = false;
166         }
167     }
168 
getPlayState()169     public int getPlayState() {
170         return mAudioTrack.getPlayState();
171     }
172 
write(ByteBuffer data, int size, long pts)173     public void write(ByteBuffer data, int size, long pts) {
174         QueueElement element = new QueueElement();
175         element.data = data;
176         element.size = size;
177         element.pts  = pts;
178 
179         // accumulate size written to queue
180         mNumBytesQueued += size;
181         mQueue.add(element);
182     }
183 }
184 
185