1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.media; 6 7import android.annotation.SuppressLint; 8import android.media.AudioFormat; 9import android.media.AudioRecord; 10import android.media.MediaRecorder.AudioSource; 11import android.media.audiofx.AcousticEchoCanceler; 12import android.media.audiofx.AudioEffect; 13import android.media.audiofx.AudioEffect.Descriptor; 14import android.os.Process; 15import android.util.Log; 16 17import org.chromium.base.CalledByNative; 18import org.chromium.base.JNINamespace; 19 20import java.nio.ByteBuffer; 21 22// Owned by its native counterpart declared in audio_record_input.h. Refer to 23// that class for general comments. 24@JNINamespace("media") 25class AudioRecordInput { 26 private static final String TAG = "AudioRecordInput"; 27 // Set to true to enable debug logs. Always check in as false. 28 private static final boolean DEBUG = false; 29 // We are unable to obtain a precise measurement of the hardware delay on 30 // Android. This is a conservative lower-bound based on measurments. It 31 // could surely be tightened with further testing. 32 private static final int HARDWARE_DELAY_MS = 100; 33 34 private final long mNativeAudioRecordInputStream; 35 private final int mSampleRate; 36 private final int mChannels; 37 private final int mBitsPerSample; 38 private final int mHardwareDelayBytes; 39 private final boolean mUsePlatformAEC; 40 private ByteBuffer mBuffer; 41 private AudioRecord mAudioRecord; 42 private AudioRecordThread mAudioRecordThread; 43 private AcousticEchoCanceler mAEC; 44 45 private class AudioRecordThread extends Thread { 46 // The "volatile" synchronization technique is discussed here: 47 // http://stackoverflow.com/a/106787/299268 48 // and more generally in this article: 49 // https://www.ibm.com/developerworks/java/library/j-jtp06197/ 50 private volatile boolean mKeepAlive = true; 51 52 @Override 53 public void run() { 54 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); 55 try { 56 mAudioRecord.startRecording(); 57 } catch (IllegalStateException e) { 58 Log.e(TAG, "startRecording failed", e); 59 return; 60 } 61 62 while (mKeepAlive) { 63 int bytesRead = mAudioRecord.read(mBuffer, mBuffer.capacity()); 64 if (bytesRead > 0) { 65 nativeOnData(mNativeAudioRecordInputStream, bytesRead, 66 mHardwareDelayBytes); 67 } else { 68 Log.e(TAG, "read failed: " + bytesRead); 69 if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) { 70 // This can happen if there is already an active 71 // AudioRecord (e.g. in another tab). 72 mKeepAlive = false; 73 } 74 } 75 } 76 77 try { 78 mAudioRecord.stop(); 79 } catch (IllegalStateException e) { 80 Log.e(TAG, "stop failed", e); 81 } 82 } 83 84 public void joinRecordThread() { 85 mKeepAlive = false; 86 while (isAlive()) { 87 try { 88 join(); 89 } catch (InterruptedException e) { 90 // Ignore. 91 } 92 } 93 } 94 } 95 96 @CalledByNative 97 private static AudioRecordInput createAudioRecordInput(long nativeAudioRecordInputStream, 98 int sampleRate, int channels, int bitsPerSample, int bytesPerBuffer, 99 boolean usePlatformAEC) { 100 return new AudioRecordInput(nativeAudioRecordInputStream, sampleRate, channels, 101 bitsPerSample, bytesPerBuffer, usePlatformAEC); 102 } 103 104 private AudioRecordInput(long nativeAudioRecordInputStream, int sampleRate, int channels, 105 int bitsPerSample, int bytesPerBuffer, boolean usePlatformAEC) { 106 mNativeAudioRecordInputStream = nativeAudioRecordInputStream; 107 mSampleRate = sampleRate; 108 mChannels = channels; 109 mBitsPerSample = bitsPerSample; 110 mHardwareDelayBytes = HARDWARE_DELAY_MS * sampleRate / 1000 * bitsPerSample / 8; 111 mUsePlatformAEC = usePlatformAEC; 112 113 // We use a direct buffer so that the native class can have access to 114 // the underlying memory address. This avoids the need to copy from a 115 // jbyteArray to native memory. More discussion of this here: 116 // http://developer.android.com/training/articles/perf-jni.html 117 mBuffer = ByteBuffer.allocateDirect(bytesPerBuffer); 118 // Rather than passing the ByteBuffer with every OnData call (requiring 119 // the potentially expensive GetDirectBufferAddress) we simply have the 120 // the native class cache the address to the memory once. 121 // 122 // Unfortunately, profiling with traceview was unable to either confirm 123 // or deny the advantage of this approach, as the values for 124 // nativeOnData() were not stable across runs. 125 nativeCacheDirectBufferAddress(mNativeAudioRecordInputStream, mBuffer); 126 } 127 128 @SuppressLint("NewApi") 129 @CalledByNative 130 private boolean open() { 131 if (mAudioRecord != null) { 132 Log.e(TAG, "open() called twice without a close()"); 133 return false; 134 } 135 int channelConfig; 136 if (mChannels == 1) { 137 channelConfig = AudioFormat.CHANNEL_IN_MONO; 138 } else if (mChannels == 2) { 139 channelConfig = AudioFormat.CHANNEL_IN_STEREO; 140 } else { 141 Log.e(TAG, "Unsupported number of channels: " + mChannels); 142 return false; 143 } 144 145 int audioFormat; 146 if (mBitsPerSample == 8) { 147 audioFormat = AudioFormat.ENCODING_PCM_8BIT; 148 } else if (mBitsPerSample == 16) { 149 audioFormat = AudioFormat.ENCODING_PCM_16BIT; 150 } else { 151 Log.e(TAG, "Unsupported bits per sample: " + mBitsPerSample); 152 return false; 153 } 154 155 // TODO(ajm): Do we need to make this larger to avoid underruns? The 156 // Android documentation notes "this size doesn't guarantee a smooth 157 // recording under load". 158 int minBufferSize = AudioRecord.getMinBufferSize(mSampleRate, channelConfig, audioFormat); 159 if (minBufferSize < 0) { 160 Log.e(TAG, "getMinBufferSize error: " + minBufferSize); 161 return false; 162 } 163 164 // We will request mBuffer.capacity() with every read call. The 165 // underlying AudioRecord buffer should be at least this large. 166 int audioRecordBufferSizeInBytes = Math.max(mBuffer.capacity(), minBufferSize); 167 try { 168 // TODO(ajm): Allow other AudioSource types to be requested? 169 mAudioRecord = new AudioRecord(AudioSource.VOICE_COMMUNICATION, 170 mSampleRate, 171 channelConfig, 172 audioFormat, 173 audioRecordBufferSizeInBytes); 174 } catch (IllegalArgumentException e) { 175 Log.e(TAG, "AudioRecord failed", e); 176 return false; 177 } 178 179 if (AcousticEchoCanceler.isAvailable()) { 180 mAEC = AcousticEchoCanceler.create(mAudioRecord.getAudioSessionId()); 181 if (mAEC == null) { 182 Log.e(TAG, "AcousticEchoCanceler.create failed"); 183 return false; 184 } 185 int ret = mAEC.setEnabled(mUsePlatformAEC); 186 if (ret != AudioEffect.SUCCESS) { 187 Log.e(TAG, "setEnabled error: " + ret); 188 return false; 189 } 190 191 if (DEBUG) { 192 Descriptor descriptor = mAEC.getDescriptor(); 193 Log.d(TAG, "AcousticEchoCanceler " + 194 "name: " + descriptor.name + ", " + 195 "implementor: " + descriptor.implementor + ", " + 196 "uuid: " + descriptor.uuid); 197 } 198 } 199 return true; 200 } 201 202 @CalledByNative 203 private void start() { 204 if (mAudioRecord == null) { 205 Log.e(TAG, "start() called before open()."); 206 return; 207 } 208 if (mAudioRecordThread != null) { 209 // start() was already called. 210 return; 211 } 212 mAudioRecordThread = new AudioRecordThread(); 213 mAudioRecordThread.start(); 214 } 215 216 @CalledByNative 217 private void stop() { 218 if (mAudioRecordThread == null) { 219 // start() was never called, or stop() was already called. 220 return; 221 } 222 mAudioRecordThread.joinRecordThread(); 223 mAudioRecordThread = null; 224 } 225 226 @SuppressLint("NewApi") 227 @CalledByNative 228 private void close() { 229 if (mAudioRecordThread != null) { 230 Log.e(TAG, "close() called before stop()."); 231 return; 232 } 233 if (mAudioRecord == null) { 234 // open() was not called. 235 return; 236 } 237 238 if (mAEC != null) { 239 mAEC.release(); 240 mAEC = null; 241 } 242 mAudioRecord.release(); 243 mAudioRecord = null; 244 } 245 246 private native void nativeCacheDirectBufferAddress(long nativeAudioRecordInputStream, 247 ByteBuffer buffer); 248 private native void nativeOnData(long nativeAudioRecordInputStream, int size, 249 int hardwareDelayBytes); 250} 251