OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 #include "media/audio/mac/audio_low_latency_input_mac.h" | 5 #include "media/audio/mac/audio_low_latency_input_mac.h" |
6 | 6 |
7 #include <CoreServices/CoreServices.h> | 7 #include <CoreServices/CoreServices.h> |
8 | 8 |
9 #include "base/basictypes.h" | 9 #include "base/basictypes.h" |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
11 #include "base/mac/mac_logging.h" | 11 #include "base/mac/mac_logging.h" |
12 #include "base/metrics/histogram_macros.h" | |
12 #include "base/metrics/sparse_histogram.h" | 13 #include "base/metrics/sparse_histogram.h" |
14 #include "base/time/time.h" | |
13 #include "media/audio/mac/audio_manager_mac.h" | 15 #include "media/audio/mac/audio_manager_mac.h" |
14 #include "media/base/audio_bus.h" | 16 #include "media/base/audio_bus.h" |
15 #include "media/base/data_buffer.h" | 17 #include "media/base/data_buffer.h" |
16 | 18 |
17 namespace media { | 19 namespace media { |
18 | 20 |
19 // Number of blocks of buffers used in the |fifo_|. | 21 // Number of blocks of buffers used in the |fifo_|. |
20 const int kNumberOfBlocksBufferInFifo = 2; | 22 const int kNumberOfBlocksBufferInFifo = 2; |
21 | 23 |
22 // Max length of sequence of TooManyFramesToProcessError errors. | 24 // Max length of sequence of TooManyFramesToProcessError errors. |
23 // The stream will be stopped as soon as this time limit is passed. | 25 // The stream will be stopped as soon as this time limit is passed. |
24 const int kMaxErrorTimeoutInSeconds = 1; | 26 const int kMaxErrorTimeoutInSeconds = 1; |
25 | 27 |
28 // A one-shot timer is created and started in Start() and it triggers | |
29 // CheckInputStartupSuccess() after this amount of time. UMA stats marked | |
30 // Media.Audio.InputStartupSuccessMac is then updated where true is added | |
31 // if input callbacks have started, and false otherwise. | |
32 const int kInputCallbackStartTimeoutInSeconds = 5; | |
33 | |
26 static std::ostream& operator<<(std::ostream& os, | 34 static std::ostream& operator<<(std::ostream& os, |
27 const AudioStreamBasicDescription& format) { | 35 const AudioStreamBasicDescription& format) { |
28 os << "sample rate : " << format.mSampleRate << std::endl | 36 os << "sample rate : " << format.mSampleRate << std::endl |
29 << "format ID : " << format.mFormatID << std::endl | 37 << "format ID : " << format.mFormatID << std::endl |
30 << "format flags : " << format.mFormatFlags << std::endl | 38 << "format flags : " << format.mFormatFlags << std::endl |
31 << "bytes per packet : " << format.mBytesPerPacket << std::endl | 39 << "bytes per packet : " << format.mBytesPerPacket << std::endl |
32 << "frames per packet : " << format.mFramesPerPacket << std::endl | 40 << "frames per packet : " << format.mFramesPerPacket << std::endl |
33 << "bytes per frame : " << format.mBytesPerFrame << std::endl | 41 << "bytes per frame : " << format.mBytesPerFrame << std::endl |
34 << "channels per frame: " << format.mChannelsPerFrame << std::endl | 42 << "channels per frame: " << format.mChannelsPerFrame << std::endl |
35 << "bits per channel : " << format.mBitsPerChannel; | 43 << "bits per channel : " << format.mBitsPerChannel; |
(...skipping 10 matching lines...) Expand all Loading... | |
46 : manager_(manager), | 54 : manager_(manager), |
47 number_of_frames_(input_params.frames_per_buffer()), | 55 number_of_frames_(input_params.frames_per_buffer()), |
48 sink_(NULL), | 56 sink_(NULL), |
49 audio_unit_(0), | 57 audio_unit_(0), |
50 input_device_id_(audio_device_id), | 58 input_device_id_(audio_device_id), |
51 started_(false), | 59 started_(false), |
52 hardware_latency_frames_(0), | 60 hardware_latency_frames_(0), |
53 number_of_channels_in_frame_(0), | 61 number_of_channels_in_frame_(0), |
54 fifo_(input_params.channels(), | 62 fifo_(input_params.channels(), |
55 number_of_frames_, | 63 number_of_frames_, |
56 kNumberOfBlocksBufferInFifo) { | 64 kNumberOfBlocksBufferInFifo), |
65 input_callback_is_active_(false) { | |
57 DCHECK(manager_); | 66 DCHECK(manager_); |
58 | 67 |
59 // Set up the desired (output) format specified by the client. | 68 // Set up the desired (output) format specified by the client. |
60 format_.mSampleRate = input_params.sample_rate(); | 69 format_.mSampleRate = input_params.sample_rate(); |
61 format_.mFormatID = kAudioFormatLinearPCM; | 70 format_.mFormatID = kAudioFormatLinearPCM; |
62 format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | | 71 format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | |
63 kLinearPCMFormatFlagIsSignedInteger; | 72 kLinearPCMFormatFlagIsSignedInteger; |
64 format_.mBitsPerChannel = input_params.bits_per_sample(); | 73 format_.mBitsPerChannel = input_params.bits_per_sample(); |
65 format_.mChannelsPerFrame = input_params.channels(); | 74 format_.mChannelsPerFrame = input_params.channels(); |
66 format_.mFramesPerPacket = 1; // uncompressed audio | 75 format_.mFramesPerPacket = 1; // uncompressed audio |
(...skipping 17 matching lines...) Expand all Loading... | |
84 AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers; | 93 AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers; |
85 audio_buffer->mNumberChannels = input_params.channels(); | 94 audio_buffer->mNumberChannels = input_params.channels(); |
86 audio_buffer->mDataByteSize = data_byte_size; | 95 audio_buffer->mDataByteSize = data_byte_size; |
87 audio_buffer->mData = audio_data_buffer_.get(); | 96 audio_buffer->mData = audio_data_buffer_.get(); |
88 } | 97 } |
89 | 98 |
90 AUAudioInputStream::~AUAudioInputStream() {} | 99 AUAudioInputStream::~AUAudioInputStream() {} |
91 | 100 |
92 // Obtain and open the AUHAL AudioOutputUnit for recording. | 101 // Obtain and open the AUHAL AudioOutputUnit for recording. |
93 bool AUAudioInputStream::Open() { | 102 bool AUAudioInputStream::Open() { |
103 DCHECK(thread_checker_.CalledOnValidThread()); | |
94 // Verify that we are not already opened. | 104 // Verify that we are not already opened. |
95 if (audio_unit_) | 105 if (audio_unit_) |
96 return false; | 106 return false; |
97 | 107 |
98 // Verify that we have a valid device. | 108 // Verify that we have a valid device. |
99 if (input_device_id_ == kAudioObjectUnknown) { | 109 if (input_device_id_ == kAudioObjectUnknown) { |
100 NOTREACHED() << "Device ID is unknown"; | 110 NOTREACHED() << "Device ID is unknown"; |
101 return false; | 111 return false; |
102 } | 112 } |
103 | 113 |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
218 hardware_latency_frames_ = GetHardwareLatency(); | 228 hardware_latency_frames_ = GetHardwareLatency(); |
219 | 229 |
220 // The master channel is 0, Left and right are channels 1 and 2. | 230 // The master channel is 0, Left and right are channels 1 and 2. |
221 // And the master channel is not counted in |number_of_channels_in_frame_|. | 231 // And the master channel is not counted in |number_of_channels_in_frame_|. |
222 number_of_channels_in_frame_ = GetNumberOfChannelsFromStream(); | 232 number_of_channels_in_frame_ = GetNumberOfChannelsFromStream(); |
223 | 233 |
224 return true; | 234 return true; |
225 } | 235 } |
226 | 236 |
227 void AUAudioInputStream::Start(AudioInputCallback* callback) { | 237 void AUAudioInputStream::Start(AudioInputCallback* callback) { |
238 DCHECK(thread_checker_.CalledOnValidThread()); | |
228 DCHECK(callback); | 239 DCHECK(callback); |
229 DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully"; | 240 DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully"; |
230 if (started_ || !audio_unit_) | 241 if (started_ || !audio_unit_) |
231 return; | 242 return; |
232 | 243 |
233 // Check if we should defer Start() for http://crbug.com/160920. | 244 // Check if we should defer Start() for http://crbug.com/160920. |
234 if (manager_->ShouldDeferStreamStart()) { | 245 if (manager_->ShouldDeferStreamStart()) { |
235 // Use a cancellable closure so that if Stop() is called before Start() | 246 // Use a cancellable closure so that if Stop() is called before Start() |
236 // actually runs, we can cancel the pending start. | 247 // actually runs, we can cancel the pending start. |
237 deferred_start_cb_.Reset(base::Bind( | 248 deferred_start_cb_.Reset(base::Bind( |
238 &AUAudioInputStream::Start, base::Unretained(this), callback)); | 249 &AUAudioInputStream::Start, base::Unretained(this), callback)); |
239 manager_->GetTaskRunner()->PostDelayedTask( | 250 manager_->GetTaskRunner()->PostDelayedTask( |
240 FROM_HERE, | 251 FROM_HERE, |
241 deferred_start_cb_.callback(), | 252 deferred_start_cb_.callback(), |
242 base::TimeDelta::FromSeconds( | 253 base::TimeDelta::FromSeconds( |
243 AudioManagerMac::kStartDelayInSecsForPowerEvents)); | 254 AudioManagerMac::kStartDelayInSecsForPowerEvents)); |
244 return; | 255 return; |
245 } | 256 } |
246 | 257 |
247 sink_ = callback; | 258 sink_ = callback; |
248 last_success_time_ = base::TimeTicks::Now(); | 259 last_success_time_ = base::TimeTicks::Now(); |
249 StartAgc(); | 260 StartAgc(); |
250 OSStatus result = AudioOutputUnitStart(audio_unit_); | 261 OSStatus result = AudioOutputUnitStart(audio_unit_); |
251 if (result == noErr) { | 262 if (result == noErr) { |
252 started_ = true; | 263 started_ = true; |
264 // For UMA stat purposes, start a one-shot timer which detects when input | |
265 // callbacks starts indicating if input audio recording works as intended. | |
266 // CheckInputStartupSuccess() will check if |input_callback_is_active_| is | |
267 // true when the timer expires. This timer delay is currently set to | |
268 // 5 seconds to avoid false alarms. | |
269 input_callback_timer_.reset(new base::OneShotTimer()); | |
270 input_callback_timer_->Start( | |
271 FROM_HERE, | |
272 base::TimeDelta::FromSeconds(kInputCallbackStartTimeoutInSeconds), this, | |
tommi (sloooow) - chröme
2015/10/30 15:08:44
I think this uses base::Unretained() and due to th
henrika (OOO until Aug 14)
2015/10/30 15:56:35
Discussed off line. Special case for Mac which doe
| |
273 &AUAudioInputStream::CheckInputStartupSuccess); | |
253 } | 274 } |
254 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) | 275 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) |
255 << "Failed to start acquiring data"; | 276 << "Failed to start acquiring data"; |
256 } | 277 } |
257 | 278 |
258 void AUAudioInputStream::Stop() { | 279 void AUAudioInputStream::Stop() { |
280 DCHECK(thread_checker_.CalledOnValidThread()); | |
259 if (!started_) | 281 if (!started_) |
260 return; | 282 return; |
261 StopAgc(); | 283 StopAgc(); |
284 input_callback_timer_.reset(); | |
262 OSStatus result = AudioOutputUnitStop(audio_unit_); | 285 OSStatus result = AudioOutputUnitStop(audio_unit_); |
263 DCHECK_EQ(result, noErr); | 286 DCHECK_EQ(result, noErr); |
287 SetInputCallbackIsActive(false); | |
264 started_ = false; | 288 started_ = false; |
265 sink_ = NULL; | 289 sink_ = NULL; |
266 fifo_.Clear(); | 290 fifo_.Clear(); |
267 | |
268 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) | 291 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) |
269 << "Failed to stop acquiring data"; | 292 << "Failed to stop acquiring data"; |
270 } | 293 } |
271 | 294 |
272 void AUAudioInputStream::Close() { | 295 void AUAudioInputStream::Close() { |
296 DCHECK(thread_checker_.CalledOnValidThread()); | |
273 // It is valid to call Close() before calling open or Start(). | 297 // It is valid to call Close() before calling open or Start(). |
274 // It is also valid to call Close() after Start() has been called. | 298 // It is also valid to call Close() after Start() has been called. |
275 if (started_) { | 299 if (started_) { |
276 Stop(); | 300 Stop(); |
277 } | 301 } |
278 if (audio_unit_) { | 302 if (audio_unit_) { |
279 // Deallocate the audio unit’s resources. | 303 // Deallocate the audio unit’s resources. |
280 OSStatus result = AudioUnitUninitialize(audio_unit_); | 304 OSStatus result = AudioUnitUninitialize(audio_unit_); |
281 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) | 305 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) |
282 << "AudioUnitUninitialize() failed."; | 306 << "AudioUnitUninitialize() failed."; |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
365 // Update the AGC volume level based on the last setting above. Note that, | 389 // Update the AGC volume level based on the last setting above. Note that, |
366 // the volume-level resolution is not infinite and it is therefore not | 390 // the volume-level resolution is not infinite and it is therefore not |
367 // possible to assume that the volume provided as input parameter can be | 391 // possible to assume that the volume provided as input parameter can be |
368 // used directly. Instead, a new query to the audio hardware is required. | 392 // used directly. Instead, a new query to the audio hardware is required. |
369 // This method does nothing if AGC is disabled. | 393 // This method does nothing if AGC is disabled. |
370 UpdateAgcVolume(); | 394 UpdateAgcVolume(); |
371 } | 395 } |
372 | 396 |
373 double AUAudioInputStream::GetVolume() { | 397 double AUAudioInputStream::GetVolume() { |
374 // Verify that we have a valid device. | 398 // Verify that we have a valid device. |
375 if (input_device_id_ == kAudioObjectUnknown){ | 399 if (input_device_id_ == kAudioObjectUnknown) { |
376 NOTREACHED() << "Device ID is unknown"; | 400 NOTREACHED() << "Device ID is unknown"; |
377 return 0.0; | 401 return 0.0; |
378 } | 402 } |
379 | 403 |
380 AudioObjectPropertyAddress property_address = { | 404 AudioObjectPropertyAddress property_address = { |
381 kAudioDevicePropertyVolumeScalar, | 405 kAudioDevicePropertyVolumeScalar, |
382 kAudioDevicePropertyScopeInput, | 406 kAudioDevicePropertyScopeInput, |
383 kAudioObjectPropertyElementMaster | 407 kAudioObjectPropertyElementMaster |
384 }; | 408 }; |
385 | 409 |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
459 UInt32 number_of_frames, | 483 UInt32 number_of_frames, |
460 AudioBufferList* io_data) { | 484 AudioBufferList* io_data) { |
461 // Verify that the correct bus is used (Input bus/Element 1) | 485 // Verify that the correct bus is used (Input bus/Element 1) |
462 DCHECK_EQ(bus_number, static_cast<UInt32>(1)); | 486 DCHECK_EQ(bus_number, static_cast<UInt32>(1)); |
463 AUAudioInputStream* audio_input = | 487 AUAudioInputStream* audio_input = |
464 reinterpret_cast<AUAudioInputStream*>(user_data); | 488 reinterpret_cast<AUAudioInputStream*>(user_data); |
465 DCHECK(audio_input); | 489 DCHECK(audio_input); |
466 if (!audio_input) | 490 if (!audio_input) |
467 return kAudioUnitErr_InvalidElement; | 491 return kAudioUnitErr_InvalidElement; |
468 | 492 |
493 // Indicate that input callbacks have started on the internal AUHAL IO | |
494 // thread. The |input_callback_is_active_| member is read from the creating | |
495 // thread when a timer fires once and set to false in Stop() on the same | |
496 // thread. It means that this thread is the only writer of | |
497 // |input_callback_is_active_| once the tread starts and it should therefore | |
498 // be safe to compare and then modify. | |
499 if (!audio_input->GetInputCallbackIsActive()) | |
tommi (sloooow) - chröme
2015/10/30 15:08:44
is this check necessary?
henrika (OOO until Aug 14)
2015/10/30 15:56:35
Not really; removed. Now only calls Set.
| |
500 audio_input->SetInputCallbackIsActive(true); | |
501 | |
469 // Update the |mDataByteSize| value in the audio_buffer_list() since | 502 // Update the |mDataByteSize| value in the audio_buffer_list() since |
470 // |number_of_frames| can be changed on the fly. | 503 // |number_of_frames| can be changed on the fly. |
471 // |mDataByteSize| needs to be exactly mapping to |number_of_frames|, | 504 // |mDataByteSize| needs to be exactly mapping to |number_of_frames|, |
472 // otherwise it will put CoreAudio into bad state and results in | 505 // otherwise it will put CoreAudio into bad state and results in |
473 // AudioUnitRender() returning -50 for the new created stream. | 506 // AudioUnitRender() returning -50 for the new created stream. |
474 // We have also seen kAudioUnitErr_TooManyFramesToProcess (-10874) and | 507 // We have also seen kAudioUnitErr_TooManyFramesToProcess (-10874) and |
475 // kAudioUnitErr_CannotDoInCurrentContext (-10863) as error codes. | 508 // kAudioUnitErr_CannotDoInCurrentContext (-10863) as error codes. |
476 // See crbug/428706 for details. | 509 // See crbug/428706 for details. |
477 UInt32 new_size = number_of_frames * audio_input->format_.mBytesPerFrame; | 510 UInt32 new_size = number_of_frames * audio_input->format_.mBytesPerFrame; |
478 AudioBuffer* audio_buffer = audio_input->audio_buffer_list()->mBuffers; | 511 AudioBuffer* audio_buffer = audio_input->audio_buffer_list()->mBuffers; |
(...skipping 231 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
710 kAudioDevicePropertyVolumeScalar, | 743 kAudioDevicePropertyVolumeScalar, |
711 kAudioDevicePropertyScopeInput, | 744 kAudioDevicePropertyScopeInput, |
712 static_cast<UInt32>(channel) | 745 static_cast<UInt32>(channel) |
713 }; | 746 }; |
714 OSStatus result = AudioObjectIsPropertySettable(input_device_id_, | 747 OSStatus result = AudioObjectIsPropertySettable(input_device_id_, |
715 &property_address, | 748 &property_address, |
716 &is_settable); | 749 &is_settable); |
717 return (result == noErr) ? is_settable : false; | 750 return (result == noErr) ? is_settable : false; |
718 } | 751 } |
719 | 752 |
753 void AUAudioInputStream::SetInputCallbackIsActive(bool enabled) { | |
754 base::subtle::Release_Store(&input_callback_is_active_, enabled); | |
755 } | |
756 | |
757 bool AUAudioInputStream::GetInputCallbackIsActive() { | |
758 return (base::subtle::Acquire_Load(&input_callback_is_active_) != false); | |
759 } | |
760 | |
761 void AUAudioInputStream::CheckInputStartupSuccess() { | |
762 DCHECK(thread_checker_.CalledOnValidThread()); | |
tommi (sloooow) - chröme
2015/10/30 15:08:44
hmm... this suggests that we're actually not on th
henrika (OOO until Aug 14)
2015/10/30 15:56:35
For the record, we are on the main thread. Special
| |
763 if (started_) { | |
764 // Check if we have called Start() and input callbacks have actually | |
765 // started in time as they should. If that is not the case, we have a | |
766 // problem and the stream is considered dead. | |
767 const bool input_callback_is_active = GetInputCallbackIsActive(); | |
768 UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartupSuccessMac", | |
769 input_callback_is_active); | |
770 DVLOG(1) << "input_callback_is_active: " << input_callback_is_active; | |
771 | |
772 if (!input_callback_is_active) { | |
773 // TODO(henrika): perhaps we should close the stream here and trigger | |
774 // HandleError with as suitable error code. | |
775 } | |
776 } | |
777 } | |
778 | |
720 } // namespace media | 779 } // namespace media |
OLD | NEW |