OLD | NEW |
1 /* | 1 /* |
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
63 private class AudioRecordThread extends Thread { | 63 private class AudioRecordThread extends Thread { |
64 private volatile boolean keepAlive = true; | 64 private volatile boolean keepAlive = true; |
65 | 65 |
66 public AudioRecordThread(String name) { | 66 public AudioRecordThread(String name) { |
67 super(name); | 67 super(name); |
68 } | 68 } |
69 | 69 |
70 @Override | 70 @Override |
71 public void run() { | 71 public void run() { |
72 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); | 72 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); |
73 Logging.w(TAG, "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo()); | 73 Logging.d(TAG, "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo()); |
74 assertTrue(audioRecord.getRecordingState() | 74 assertTrue(audioRecord.getRecordingState() |
75 == AudioRecord.RECORDSTATE_RECORDING); | 75 == AudioRecord.RECORDSTATE_RECORDING); |
76 | 76 |
77 long lastTime = System.nanoTime(); | 77 long lastTime = System.nanoTime(); |
78 while (keepAlive) { | 78 while (keepAlive) { |
79 int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity()); | 79 int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity()); |
80 if (bytesRead == byteBuffer.capacity()) { | 80 if (bytesRead == byteBuffer.capacity()) { |
81 nativeDataIsRecorded(bytesRead, nativeAudioRecord); | 81 nativeDataIsRecorded(bytesRead, nativeAudioRecord); |
82 } else { | 82 } else { |
83 Logging.e(TAG,"AudioRecord.read failed: " + bytesRead); | 83 Logging.e(TAG,"AudioRecord.read failed: " + bytesRead); |
84 if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) { | 84 if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) { |
85 keepAlive = false; | 85 keepAlive = false; |
86 } | 86 } |
87 } | 87 } |
88 if (DEBUG) { | 88 if (DEBUG) { |
89 long nowTime = System.nanoTime(); | 89 long nowTime = System.nanoTime(); |
90 long durationInMs = | 90 long durationInMs = |
91 TimeUnit.NANOSECONDS.toMillis((nowTime - lastTime)); | 91 TimeUnit.NANOSECONDS.toMillis((nowTime - lastTime)); |
92 lastTime = nowTime; | 92 lastTime = nowTime; |
93 Logging.w(TAG, "bytesRead[" + durationInMs + "] " + bytesRead); | 93 Logging.d(TAG, "bytesRead[" + durationInMs + "] " + bytesRead); |
94 } | 94 } |
95 } | 95 } |
96 | 96 |
97 try { | 97 try { |
98 audioRecord.stop(); | 98 audioRecord.stop(); |
99 } catch (IllegalStateException e) { | 99 } catch (IllegalStateException e) { |
100 Logging.e(TAG,"AudioRecord.stop failed: " + e.getMessage()); | 100 Logging.e(TAG,"AudioRecord.stop failed: " + e.getMessage()); |
101 } | 101 } |
102 } | 102 } |
103 | 103 |
104 public void joinThread() { | 104 public void joinThread() { |
105 keepAlive = false; | 105 keepAlive = false; |
106 while (isAlive()) { | 106 while (isAlive()) { |
107 try { | 107 try { |
108 join(); | 108 join(); |
109 } catch (InterruptedException e) { | 109 } catch (InterruptedException e) { |
110 // Ignore. | 110 // Ignore. |
111 } | 111 } |
112 } | 112 } |
113 } | 113 } |
114 } | 114 } |
115 | 115 |
116 WebRtcAudioRecord(Context context, long nativeAudioRecord) { | 116 WebRtcAudioRecord(Context context, long nativeAudioRecord) { |
117 Logging.w(TAG, "ctor" + WebRtcAudioUtils.getThreadInfo()); | 117 Logging.d(TAG, "ctor" + WebRtcAudioUtils.getThreadInfo()); |
118 this.context = context; | 118 this.context = context; |
119 this.nativeAudioRecord = nativeAudioRecord; | 119 this.nativeAudioRecord = nativeAudioRecord; |
120 if (DEBUG) { | 120 if (DEBUG) { |
121 WebRtcAudioUtils.logDeviceInfo(TAG); | 121 WebRtcAudioUtils.logDeviceInfo(TAG); |
122 } | 122 } |
123 effects = WebRtcAudioEffects.create(); | 123 effects = WebRtcAudioEffects.create(); |
124 } | 124 } |
125 | 125 |
126 private boolean enableBuiltInAEC(boolean enable) { | 126 private boolean enableBuiltInAEC(boolean enable) { |
127 Logging.w(TAG, "enableBuiltInAEC(" + enable + ')'); | 127 Logging.d(TAG, "enableBuiltInAEC(" + enable + ')'); |
128 if (effects == null) { | 128 if (effects == null) { |
129 Logging.e(TAG,"Built-in AEC is not supported on this platform"); | 129 Logging.e(TAG,"Built-in AEC is not supported on this platform"); |
130 return false; | 130 return false; |
131 } | 131 } |
132 return effects.setAEC(enable); | 132 return effects.setAEC(enable); |
133 } | 133 } |
134 | 134 |
135 private boolean enableBuiltInAGC(boolean enable) { | 135 private boolean enableBuiltInAGC(boolean enable) { |
136 Logging.w(TAG, "enableBuiltInAGC(" + enable + ')'); | 136 Logging.d(TAG, "enableBuiltInAGC(" + enable + ')'); |
137 if (effects == null) { | 137 if (effects == null) { |
138 Logging.e(TAG,"Built-in AGC is not supported on this platform"); | 138 Logging.e(TAG,"Built-in AGC is not supported on this platform"); |
139 return false; | 139 return false; |
140 } | 140 } |
141 return effects.setAGC(enable); | 141 return effects.setAGC(enable); |
142 } | 142 } |
143 | 143 |
144 private boolean enableBuiltInNS(boolean enable) { | 144 private boolean enableBuiltInNS(boolean enable) { |
145 Logging.w(TAG, "enableBuiltInNS(" + enable + ')'); | 145 Logging.d(TAG, "enableBuiltInNS(" + enable + ')'); |
146 if (effects == null) { | 146 if (effects == null) { |
147 Logging.e(TAG,"Built-in NS is not supported on this platform"); | 147 Logging.e(TAG,"Built-in NS is not supported on this platform"); |
148 return false; | 148 return false; |
149 } | 149 } |
150 return effects.setNS(enable); | 150 return effects.setNS(enable); |
151 } | 151 } |
152 | 152 |
153 private int initRecording(int sampleRate, int channels) { | 153 private int initRecording(int sampleRate, int channels) { |
154 Logging.w(TAG, "initRecording(sampleRate=" + sampleRate + ", channels=" + | 154 Logging.d(TAG, "initRecording(sampleRate=" + sampleRate + ", channels=" + |
155 channels + ")"); | 155 channels + ")"); |
156 if (!WebRtcAudioUtils.hasPermission( | 156 if (!WebRtcAudioUtils.hasPermission( |
157 context, android.Manifest.permission.RECORD_AUDIO)) { | 157 context, android.Manifest.permission.RECORD_AUDIO)) { |
158 Logging.e(TAG,"RECORD_AUDIO permission is missing"); | 158 Logging.e(TAG,"RECORD_AUDIO permission is missing"); |
159 return -1; | 159 return -1; |
160 } | 160 } |
161 if (audioRecord != null) { | 161 if (audioRecord != null) { |
162 Logging.e(TAG,"InitRecording() called twice without StopRecording()"); | 162 Logging.e(TAG,"InitRecording() called twice without StopRecording()"); |
163 return -1; | 163 return -1; |
164 } | 164 } |
165 final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8); | 165 final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8); |
166 final int framesPerBuffer = sampleRate / BUFFERS_PER_SECOND; | 166 final int framesPerBuffer = sampleRate / BUFFERS_PER_SECOND; |
167 byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer); | 167 byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer); |
168 Logging.w(TAG, "byteBuffer.capacity: " + byteBuffer.capacity()); | 168 Logging.d(TAG, "byteBuffer.capacity: " + byteBuffer.capacity()); |
169 // Rather than passing the ByteBuffer with every callback (requiring | 169 // Rather than passing the ByteBuffer with every callback (requiring |
170 // the potentially expensive GetDirectBufferAddress) we simply have the | 170 // the potentially expensive GetDirectBufferAddress) we simply have the |
171 // the native class cache the address to the memory once. | 171 // the native class cache the address to the memory once. |
172 nativeCacheDirectBufferAddress(byteBuffer, nativeAudioRecord); | 172 nativeCacheDirectBufferAddress(byteBuffer, nativeAudioRecord); |
173 | 173 |
174 // Get the minimum buffer size required for the successful creation of | 174 // Get the minimum buffer size required for the successful creation of |
175 // an AudioRecord object, in byte units. | 175 // an AudioRecord object, in byte units. |
176 // Note that this size doesn't guarantee a smooth recording under load. | 176 // Note that this size doesn't guarantee a smooth recording under load. |
177 int minBufferSize = AudioRecord.getMinBufferSize( | 177 int minBufferSize = AudioRecord.getMinBufferSize( |
178 sampleRate, | 178 sampleRate, |
179 AudioFormat.CHANNEL_IN_MONO, | 179 AudioFormat.CHANNEL_IN_MONO, |
180 AudioFormat.ENCODING_PCM_16BIT); | 180 AudioFormat.ENCODING_PCM_16BIT); |
181 if (minBufferSize == AudioRecord.ERROR | 181 if (minBufferSize == AudioRecord.ERROR |
182 || minBufferSize == AudioRecord.ERROR_BAD_VALUE) { | 182 || minBufferSize == AudioRecord.ERROR_BAD_VALUE) { |
183 Logging.e(TAG, "AudioRecord.getMinBufferSize failed: " + minBufferSize); | 183 Logging.e(TAG, "AudioRecord.getMinBufferSize failed: " + minBufferSize); |
184 return -1; | 184 return -1; |
185 } | 185 } |
186 Logging.w(TAG, "AudioRecord.getMinBufferSize: " + minBufferSize); | 186 Logging.d(TAG, "AudioRecord.getMinBufferSize: " + minBufferSize); |
187 | 187 |
188 // Use a larger buffer size than the minimum required when creating the | 188 // Use a larger buffer size than the minimum required when creating the |
189 // AudioRecord instance to ensure smooth recording under load. It has been | 189 // AudioRecord instance to ensure smooth recording under load. It has been |
190 // verified that it does not increase the actual recording latency. | 190 // verified that it does not increase the actual recording latency. |
191 int bufferSizeInBytes = | 191 int bufferSizeInBytes = |
192 Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity()); | 192 Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity()); |
193 Logging.w(TAG, "bufferSizeInBytes: " + bufferSizeInBytes); | 193 Logging.d(TAG, "bufferSizeInBytes: " + bufferSizeInBytes); |
194 try { | 194 try { |
195 audioRecord = new AudioRecord(AudioSource.VOICE_COMMUNICATION, | 195 audioRecord = new AudioRecord(AudioSource.VOICE_COMMUNICATION, |
196 sampleRate, | 196 sampleRate, |
197 AudioFormat.CHANNEL_IN_MONO, | 197 AudioFormat.CHANNEL_IN_MONO, |
198 AudioFormat.ENCODING_PCM_16BIT, | 198 AudioFormat.ENCODING_PCM_16BIT, |
199 bufferSizeInBytes); | 199 bufferSizeInBytes); |
200 } catch (IllegalArgumentException e) { | 200 } catch (IllegalArgumentException e) { |
201 Logging.e(TAG,e.getMessage()); | 201 Logging.e(TAG,e.getMessage()); |
202 return -1; | 202 return -1; |
203 } | 203 } |
204 if (audioRecord == null || | 204 if (audioRecord == null || |
205 audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { | 205 audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { |
206 Logging.e(TAG,"Failed to create a new AudioRecord instance"); | 206 Logging.e(TAG,"Failed to create a new AudioRecord instance"); |
207 return -1; | 207 return -1; |
208 } | 208 } |
209 Logging.w(TAG, "AudioRecord " | 209 Logging.d(TAG, "AudioRecord " |
210 + "session ID: " + audioRecord.getAudioSessionId() + ", " | 210 + "session ID: " + audioRecord.getAudioSessionId() + ", " |
211 + "audio format: " + audioRecord.getAudioFormat() + ", " | 211 + "audio format: " + audioRecord.getAudioFormat() + ", " |
212 + "channels: " + audioRecord.getChannelCount() + ", " | 212 + "channels: " + audioRecord.getChannelCount() + ", " |
213 + "sample rate: " + audioRecord.getSampleRate()); | 213 + "sample rate: " + audioRecord.getSampleRate()); |
214 if (effects != null) { | 214 if (effects != null) { |
215 effects.enable(audioRecord.getAudioSessionId()); | 215 effects.enable(audioRecord.getAudioSessionId()); |
216 } | 216 } |
217 if (WebRtcAudioUtils.runningOnMOrHigher()) { | 217 if (WebRtcAudioUtils.runningOnMOrHigher()) { |
218 // Returns the frame count of the native AudioRecord buffer. This is | 218 // Returns the frame count of the native AudioRecord buffer. This is |
219 // greater than or equal to the bufferSizeInBytes converted to frame | 219 // greater than or equal to the bufferSizeInBytes converted to frame |
220 // units. The native frame count may be enlarged to accommodate the | 220 // units. The native frame count may be enlarged to accommodate the |
221 // requirements of the source on creation or if the AudioRecord is | 221 // requirements of the source on creation or if the AudioRecord is |
222 // subsequently rerouted. | 222 // subsequently rerouted. |
223 Logging.d(TAG, "bufferSizeInFrames: " | 223 Logging.d(TAG, "bufferSizeInFrames: " |
224 + audioRecord.getBufferSizeInFrames()); | 224 + audioRecord.getBufferSizeInFrames()); |
225 } | 225 } |
226 return framesPerBuffer; | 226 return framesPerBuffer; |
227 } | 227 } |
228 | 228 |
229 private boolean startRecording() { | 229 private boolean startRecording() { |
230 Logging.w(TAG, "startRecording"); | 230 Logging.d(TAG, "startRecording"); |
231 assertTrue(audioRecord != null); | 231 assertTrue(audioRecord != null); |
232 assertTrue(audioThread == null); | 232 assertTrue(audioThread == null); |
233 try { | 233 try { |
234 audioRecord.startRecording(); | 234 audioRecord.startRecording(); |
235 } catch (IllegalStateException e) { | 235 } catch (IllegalStateException e) { |
236 Logging.e(TAG,"AudioRecord.startRecording failed: " + e.getMessage()); | 236 Logging.e(TAG,"AudioRecord.startRecording failed: " + e.getMessage()); |
237 return false; | 237 return false; |
238 } | 238 } |
239 if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { | 239 if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { |
240 Logging.e(TAG,"AudioRecord.startRecording failed"); | 240 Logging.e(TAG,"AudioRecord.startRecording failed"); |
241 return false; | 241 return false; |
242 } | 242 } |
243 audioThread = new AudioRecordThread("AudioRecordJavaThread"); | 243 audioThread = new AudioRecordThread("AudioRecordJavaThread"); |
244 audioThread.start(); | 244 audioThread.start(); |
245 return true; | 245 return true; |
246 } | 246 } |
247 | 247 |
248 private boolean stopRecording() { | 248 private boolean stopRecording() { |
249 Logging.w(TAG, "stopRecording"); | 249 Logging.d(TAG, "stopRecording"); |
250 assertTrue(audioThread != null); | 250 assertTrue(audioThread != null); |
251 audioThread.joinThread(); | 251 audioThread.joinThread(); |
252 audioThread = null; | 252 audioThread = null; |
253 if (effects != null) { | 253 if (effects != null) { |
254 effects.release(); | 254 effects.release(); |
255 } | 255 } |
256 audioRecord.release(); | 256 audioRecord.release(); |
257 audioRecord = null; | 257 audioRecord = null; |
258 return true; | 258 return true; |
259 } | 259 } |
260 | 260 |
261 // Helper method which throws an exception when an assertion has failed. | 261 // Helper method which throws an exception when an assertion has failed. |
262 private static void assertTrue(boolean condition) { | 262 private static void assertTrue(boolean condition) { |
263 if (!condition) { | 263 if (!condition) { |
264 throw new AssertionError("Expected condition to be true"); | 264 throw new AssertionError("Expected condition to be true"); |
265 } | 265 } |
266 } | 266 } |
267 | 267 |
268 private native void nativeCacheDirectBufferAddress( | 268 private native void nativeCacheDirectBufferAddress( |
269 ByteBuffer byteBuffer, long nativeAudioRecord); | 269 ByteBuffer byteBuffer, long nativeAudioRecord); |
270 | 270 |
271 private native void nativeDataIsRecorded(int bytes, long nativeAudioRecord); | 271 private native void nativeDataIsRecorded(int bytes, long nativeAudioRecord); |
272 } | 272 } |
OLD | NEW |