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 |