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