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