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.util.Log; 22 23 import java.util.LinkedList; 24 25 /** 26 * Class for playing audio by using audio track. 27 * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods will 28 * block until all data has been written to system. In order to avoid blocking, this class 29 * caculates available buffer size first then writes to audio sink. 30 */ 31 public class NonBlockingAudioTrack { 32 private static final String TAG = NonBlockingAudioTrack.class.getSimpleName(); 33 34 class QueueElem { 35 byte[] data; 36 int offset; 37 int size; 38 } 39 40 private AudioTrack mAudioTrack; 41 private boolean mWriteMorePending = false; 42 private int mSampleRate; 43 private int mFrameSize; 44 private int mBufferSizeInFrames; 45 private int mNumFramesSubmitted = 0; 46 private int mNumBytesQueued = 0; 47 private LinkedList<QueueElem> mQueue = new LinkedList<QueueElem>(); 48 NonBlockingAudioTrack(int sampleRate, int channelCount)49 public NonBlockingAudioTrack(int sampleRate, int channelCount) { 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 mAudioTrack = new AudioTrack( 74 AudioManager.STREAM_MUSIC, 75 sampleRate, 76 channelConfig, 77 AudioFormat.ENCODING_PCM_16BIT, 78 bufferSize, 79 AudioTrack.MODE_STREAM); 80 81 mSampleRate = sampleRate; 82 mFrameSize = 2 * channelCount; 83 mBufferSizeInFrames = bufferSize / mFrameSize; 84 } 85 getAudioTimeUs()86 public long getAudioTimeUs() { 87 int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition(); 88 89 return (numFramesPlayed * 1000000L) / mSampleRate; 90 } 91 getNumBytesQueued()92 public int getNumBytesQueued() { 93 return mNumBytesQueued; 94 } 95 play()96 public void play() { 97 mAudioTrack.play(); 98 } 99 stop()100 public void stop() { 101 cancelWriteMore(); 102 103 mAudioTrack.stop(); 104 105 mNumFramesSubmitted = 0; 106 mQueue.clear(); 107 mNumBytesQueued = 0; 108 } 109 pause()110 public void pause() { 111 cancelWriteMore(); 112 113 mAudioTrack.pause(); 114 } 115 release()116 public void release() { 117 cancelWriteMore(); 118 119 mAudioTrack.release(); 120 mAudioTrack = null; 121 } 122 process()123 public void process() { 124 mWriteMorePending = false; 125 writeMore(); 126 } 127 getPlayState()128 public int getPlayState() { 129 return mAudioTrack.getPlayState(); 130 } 131 writeMore()132 private void writeMore() { 133 if (mQueue.isEmpty()) { 134 return; 135 } 136 137 int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition(); 138 int numFramesPending = mNumFramesSubmitted - numFramesPlayed; 139 int numFramesAvailableToWrite = mBufferSizeInFrames - numFramesPending; 140 int numBytesAvailableToWrite = numFramesAvailableToWrite * mFrameSize; 141 142 while (numBytesAvailableToWrite > 0) { 143 QueueElem elem = mQueue.peekFirst(); 144 145 int numBytes = elem.size; 146 if (numBytes > numBytesAvailableToWrite) { 147 numBytes = numBytesAvailableToWrite; 148 } 149 150 int written = mAudioTrack.write(elem.data, elem.offset, numBytes); 151 assert(written == numBytes); 152 153 mNumFramesSubmitted += written / mFrameSize; 154 155 elem.size -= numBytes; 156 numBytesAvailableToWrite -= numBytes; 157 mNumBytesQueued -= numBytes; 158 159 if (elem.size == 0) { 160 mQueue.removeFirst(); 161 162 if (mQueue.isEmpty()) { 163 break; 164 } 165 } else { 166 elem.offset += numBytes; 167 } 168 } 169 170 if (!mQueue.isEmpty()) { 171 scheduleWriteMore(); 172 } 173 } 174 scheduleWriteMore()175 private void scheduleWriteMore() { 176 if (mWriteMorePending) { 177 return; 178 } 179 180 int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition(); 181 int numFramesPending = mNumFramesSubmitted - numFramesPlayed; 182 int pendingDurationMs = 1000 * numFramesPending / mSampleRate; 183 184 mWriteMorePending = true; 185 } 186 cancelWriteMore()187 private void cancelWriteMore() { 188 mWriteMorePending = false; 189 } 190 write(byte[] data, int size)191 public void write(byte[] data, int size) { 192 QueueElem elem = new QueueElem(); 193 elem.data = data; 194 elem.offset = 0; 195 elem.size = size; 196 197 // accumulate size written to queue 198 mNumBytesQueued += size; 199 mQueue.add(elem); 200 } 201 } 202 203