1 /* 2 * Copyright (C) 2017 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 17 package com.android.cts.verifier.audio.audiolib; 18 19 import android.content.Context; 20 import android.media.AudioDeviceInfo; 21 import android.media.AudioFormat; 22 import android.media.AudioManager; 23 import android.media.AudioTrack; 24 25 import android.util.Log; 26 27 /** 28 * Plays audio data from a stream. Audio data comes from a provided AudioFiller subclass instance. 29 */ 30 public class StreamPlayer { 31 @SuppressWarnings("unused") 32 private static String TAG = "StreamPlayer"; 33 34 private int mSampleRate; 35 private int mNumChans; 36 37 private AudioFiller mFiller; 38 39 private Thread mPlayerThread; 40 41 private AudioTrack mAudioTrack; 42 private int mNumAudioTrackFrames; // number of frames for INTERNAL AudioTrack buffer 43 44 // The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack. 45 private int mNumBurstFrames; 46 private float[] mBurstBuffer; 47 48 private float[] mChanGains; 49 private volatile boolean mPlaying; 50 51 private AudioDeviceInfo mRoutingDevice; 52 StreamPlayer()53 public StreamPlayer() {} 54 getSampleRate()55 public int getSampleRate() { return mSampleRate; } getNumBurstFrames()56 public int getNumBurstFrames() { return mNumBurstFrames; } 57 setChanGains(float[] chanGains)58 public void setChanGains(float[] chanGains) { 59 mChanGains = chanGains; 60 } 61 isPlaying()62 public boolean isPlaying() { return mPlaying; } 63 applyChannelGains()64 private void applyChannelGains() { 65 if (mChanGains != null) { 66 int buffIndex = 0; 67 for (int frame = 0; frame < mNumBurstFrames; frame++) { 68 for (int chan = 0; chan < mNumChans; chan++) { 69 mBurstBuffer[buffIndex++] *= mChanGains[chan]; 70 } 71 } 72 } 73 } 74 setFiller(AudioFiller filler)75 public void setFiller(AudioFiller filler) { mFiller = filler; } 76 setRouting(AudioDeviceInfo routingDevice)77 public void setRouting(AudioDeviceInfo routingDevice) { 78 mRoutingDevice = routingDevice; 79 if (mAudioTrack != null) { 80 mAudioTrack.setPreferredDevice(mRoutingDevice); 81 } 82 } 83 getAudioTrack()84 public AudioTrack getAudioTrack() { return mAudioTrack; } 85 allocBurstBuffer()86 private void allocBurstBuffer() { 87 mBurstBuffer = new float[mNumBurstFrames * mNumChans]; 88 } 89 calcNumBufferBytes(int sampleRate, int numChannels, int encoding)90 private static int calcNumBufferBytes(int sampleRate, int numChannels, int encoding) { 91 return AudioTrack.getMinBufferSize(sampleRate, 92 AudioUtils.countToOutPositionMask(numChannels), 93 encoding); 94 } 95 calcNumBufferFrames(int sampleRate, int numChannels, int encoding)96 private static int calcNumBufferFrames(int sampleRate, int numChannels, int encoding) { 97 return calcNumBufferBytes(sampleRate, numChannels, encoding) 98 / AudioUtils.calcFrameSizeInBytes(encoding, numChannels); 99 } 100 calcNumBurstFrames(AudioManager am)101 public static int calcNumBurstFrames(AudioManager am) { 102 String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 103 return Integer.parseInt(framesPerBuffer, 10); 104 } 105 open(int numChans, int sampleRate, int numBurstFrames, AudioFiller filler)106 public boolean open(int numChans, int sampleRate, int numBurstFrames, AudioFiller filler) { 107 // Log.i(TAG, "StreamPlayer.open(chans:" + numChans + ", rate:" + sampleRate + 108 // ", frames:" + numBurstFrames); 109 110 mNumChans = numChans; 111 mSampleRate = sampleRate; 112 mNumBurstFrames = numBurstFrames; 113 114 mNumAudioTrackFrames = 115 calcNumBufferFrames(sampleRate, numChans, AudioFormat.ENCODING_PCM_FLOAT); 116 117 mFiller = filler; 118 119 int bufferSizeInBytes = mNumAudioTrackFrames * 120 AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChans); 121 try { 122 mAudioTrack = new AudioTrack.Builder() 123 .setAudioFormat(new AudioFormat.Builder() 124 .setEncoding(AudioFormat.ENCODING_PCM_FLOAT) 125 .setSampleRate(mSampleRate) 126 .setChannelIndexMask(AudioUtils.countToIndexMask(mNumChans)) 127 .build()) 128 .setBufferSizeInBytes(bufferSizeInBytes) 129 .build(); 130 131 allocBurstBuffer(); 132 return true; 133 } catch (UnsupportedOperationException ex) { 134 Log.i(TAG, "Couldn't open AudioTrack: " + ex); 135 mAudioTrack = null; 136 return false; 137 } 138 } 139 waitForPlayerThreadToExit()140 private void waitForPlayerThreadToExit() { 141 try { 142 if (mPlayerThread != null) { 143 mPlayerThread.join(); 144 mPlayerThread = null; 145 } 146 } catch (InterruptedException e) { 147 e.printStackTrace(); 148 } 149 } 150 close()151 public void close() { 152 stop(); 153 154 waitForPlayerThreadToExit(); 155 156 if (mAudioTrack != null) { 157 mAudioTrack.release(); 158 mAudioTrack = null; 159 } 160 } 161 start()162 public boolean start() { 163 if (!mPlaying && mAudioTrack != null) { 164 mPlaying = true; 165 166 waitForPlayerThreadToExit(); // just to be sure. 167 168 mPlayerThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread"); 169 mPlayerThread.start(); 170 171 return true; 172 } 173 174 return false; 175 } 176 stop()177 public void stop() { 178 mPlaying = false; 179 } 180 181 // 182 // StreamPlayerRunnable 183 // 184 private class StreamPlayerRunnable implements Runnable { 185 @Override run()186 public void run() { 187 final int numBurstSamples = mNumBurstFrames * mNumChans; 188 189 mAudioTrack.play(); 190 while (true) { 191 boolean playing; 192 synchronized(this) { 193 playing = mPlaying; 194 } 195 if (!playing) { 196 break; 197 } 198 199 mFiller.fill(mBurstBuffer, mNumBurstFrames, mNumChans); 200 if (mChanGains != null) { 201 applyChannelGains(); 202 } 203 int numSamplesWritten = 204 mAudioTrack.write(mBurstBuffer, 0, numBurstSamples, AudioTrack.WRITE_BLOCKING); 205 if (numSamplesWritten < 0) { 206 // error 207 Log.i(TAG, "AudioTrack write error: " + numSamplesWritten); 208 stop(); 209 } else if (numSamplesWritten < numBurstSamples) { 210 // end of stream 211 Log.i(TAG, "Stream Complete."); 212 stop(); 213 } 214 } 215 } 216 } 217 } 218