audio_low_latency_input_mac.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 3// found in the LICENSE file. 4 5#include "media/audio/mac/audio_low_latency_input_mac.h" 6 7#include <CoreServices/CoreServices.h> 8 9#include "base/basictypes.h" 10#include "base/logging.h" 11#include "base/mac/mac_logging.h" 12#include "media/audio/audio_util.h" 13#include "media/audio/mac/audio_manager_mac.h" 14#include "media/base/data_buffer.h" 15 16namespace media { 17 18static const int kMinIntervalBetweenVolumeUpdatesMs = 1000; 19 20static std::ostream& operator<<(std::ostream& os, 21 const AudioStreamBasicDescription& format) { 22 os << "sample rate : " << format.mSampleRate << std::endl 23 << "format ID : " << format.mFormatID << std::endl 24 << "format flags : " << format.mFormatFlags << std::endl 25 << "bytes per packet : " << format.mBytesPerPacket << std::endl 26 << "frames per packet : " << format.mFramesPerPacket << std::endl 27 << "bytes per frame : " << format.mBytesPerFrame << std::endl 28 << "channels per frame: " << format.mChannelsPerFrame << std::endl 29 << "bits per channel : " << format.mBitsPerChannel; 30 return os; 31} 32 33// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit" 34// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html 35// for more details and background regarding this implementation. 36 37AUAudioInputStream::AUAudioInputStream( 38 AudioManagerMac* manager, const AudioParameters& params, 39 AudioDeviceID audio_device_id) 40 : manager_(manager), 41 sink_(NULL), 42 audio_unit_(0), 43 input_device_id_(audio_device_id), 44 started_(false), 45 hardware_latency_frames_(0), 46 number_of_channels_in_frame_(0) { 47 DCHECK(manager_); 48 49 // Set up the desired (output) format specified by the client. 50 format_.mSampleRate = params.sample_rate(); 51 format_.mFormatID = kAudioFormatLinearPCM; 52 format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | 53 kLinearPCMFormatFlagIsSignedInteger; 54 format_.mBitsPerChannel = params.bits_per_sample(); 55 format_.mChannelsPerFrame = params.channels(); 56 format_.mFramesPerPacket = 1; // uncompressed audio 57 format_.mBytesPerPacket = (format_.mBitsPerChannel * 58 params.channels()) / 8; 59 format_.mBytesPerFrame = format_.mBytesPerPacket; 60 format_.mReserved = 0; 61 62 DVLOG(1) << "Desired ouput format: " << format_; 63 64 // Set number of sample frames per callback used by the internal audio layer. 65 // An internal FIFO is then utilized to adapt the internal size to the size 66 // requested by the client. 67 // Note that we use the same native buffer size as for the output side here 68 // since the AUHAL implementation requires that both capture and render side 69 // use the same buffer size. See http://crbug.com/154352 for more details. 70 number_of_frames_ = GetAudioHardwareBufferSize(); 71 DVLOG(1) << "Size of data buffer in frames : " << number_of_frames_; 72 73 // Derive size (in bytes) of the buffers that we will render to. 74 UInt32 data_byte_size = number_of_frames_ * format_.mBytesPerFrame; 75 DVLOG(1) << "Size of data buffer in bytes : " << data_byte_size; 76 77 // Allocate AudioBuffers to be used as storage for the received audio. 78 // The AudioBufferList structure works as a placeholder for the 79 // AudioBuffer structure, which holds a pointer to the actual data buffer. 80 audio_data_buffer_.reset(new uint8[data_byte_size]); 81 audio_buffer_list_.mNumberBuffers = 1; 82 83 AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers; 84 audio_buffer->mNumberChannels = params.channels(); 85 audio_buffer->mDataByteSize = data_byte_size; 86 audio_buffer->mData = audio_data_buffer_.get(); 87 88 // Set up an internal FIFO buffer that will accumulate recorded audio frames 89 // until a requested size is ready to be sent to the client. 90 // It is not possible to ask for less than |kAudioFramesPerCallback| number of 91 // audio frames. 92 const size_t requested_size_frames = 93 params.GetBytesPerBuffer() / format_.mBytesPerPacket; 94 DCHECK_GE(requested_size_frames, number_of_frames_); 95 requested_size_bytes_ = requested_size_frames * format_.mBytesPerFrame; 96 DVLOG(1) << "Requested buffer size in bytes : " << requested_size_bytes_; 97 DLOG_IF(INFO, requested_size_frames > number_of_frames_) << "FIFO is used"; 98 99 // Allocate some extra memory to avoid memory reallocations. 100 // Ensure that the size is an even multiple of |number_of_frames_ and 101 // larger than |requested_size_frames|. 102 // Example: number_of_frames_=128, requested_size_frames=480 => 103 // allocated space equals 4*128=512 audio frames 104 const int max_forward_capacity = format_.mBytesPerFrame * number_of_frames_ * 105 ((requested_size_frames / number_of_frames_) + 1); 106 fifo_.reset(new media::SeekableBuffer(0, max_forward_capacity)); 107 108 data_ = new media::DataBuffer(requested_size_bytes_); 109} 110 111AUAudioInputStream::~AUAudioInputStream() {} 112 113// Obtain and open the AUHAL AudioOutputUnit for recording. 114bool AUAudioInputStream::Open() { 115 // Verify that we are not already opened. 116 if (audio_unit_) 117 return false; 118 119 // Verify that we have a valid device. 120 if (input_device_id_ == kAudioObjectUnknown) { 121 NOTREACHED() << "Device ID is unknown"; 122 return false; 123 } 124 125 // Start by obtaining an AudioOuputUnit using an AUHAL component description. 126 127 Component comp; 128 ComponentDescription desc; 129 130 // Description for the Audio Unit we want to use (AUHAL in this case). 131 desc.componentType = kAudioUnitType_Output; 132 desc.componentSubType = kAudioUnitSubType_HALOutput; 133 desc.componentManufacturer = kAudioUnitManufacturer_Apple; 134 desc.componentFlags = 0; 135 desc.componentFlagsMask = 0; 136 comp = FindNextComponent(0, &desc); 137 DCHECK(comp); 138 139 // Get access to the service provided by the specified Audio Unit. 140 OSStatus result = OpenAComponent(comp, &audio_unit_); 141 if (result) { 142 HandleError(result); 143 return false; 144 } 145 146 // Enable IO on the input scope of the Audio Unit. 147 148 // After creating the AUHAL object, we must enable IO on the input scope 149 // of the Audio Unit to obtain the device input. Input must be explicitly 150 // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1 151 // of the AUHAL. Beacause the AUHAL can be used for both input and output, 152 // we must also disable IO on the output scope. 153 154 UInt32 enableIO = 1; 155 156 // Enable input on the AUHAL. 157 result = AudioUnitSetProperty(audio_unit_, 158 kAudioOutputUnitProperty_EnableIO, 159 kAudioUnitScope_Input, 160 1, // input element 1 161 &enableIO, // enable 162 sizeof(enableIO)); 163 if (result) { 164 HandleError(result); 165 return false; 166 } 167 168 // Disable output on the AUHAL. 169 enableIO = 0; 170 result = AudioUnitSetProperty(audio_unit_, 171 kAudioOutputUnitProperty_EnableIO, 172 kAudioUnitScope_Output, 173 0, // output element 0 174 &enableIO, // disable 175 sizeof(enableIO)); 176 if (result) { 177 HandleError(result); 178 return false; 179 } 180 181 // Next, set the audio device to be the Audio Unit's current device. 182 // Note that, devices can only be set to the AUHAL after enabling IO. 183 result = AudioUnitSetProperty(audio_unit_, 184 kAudioOutputUnitProperty_CurrentDevice, 185 kAudioUnitScope_Global, 186 0, 187 &input_device_id_, 188 sizeof(input_device_id_)); 189 if (result) { 190 HandleError(result); 191 return false; 192 } 193 194 // Register the input procedure for the AUHAL. 195 // This procedure will be called when the AUHAL has received new data 196 // from the input device. 197 AURenderCallbackStruct callback; 198 callback.inputProc = InputProc; 199 callback.inputProcRefCon = this; 200 result = AudioUnitSetProperty(audio_unit_, 201 kAudioOutputUnitProperty_SetInputCallback, 202 kAudioUnitScope_Global, 203 0, 204 &callback, 205 sizeof(callback)); 206 if (result) { 207 HandleError(result); 208 return false; 209 } 210 211 // Set up the the desired (output) format. 212 // For obtaining input from a device, the device format is always expressed 213 // on the output scope of the AUHAL's Element 1. 214 result = AudioUnitSetProperty(audio_unit_, 215 kAudioUnitProperty_StreamFormat, 216 kAudioUnitScope_Output, 217 1, 218 &format_, 219 sizeof(format_)); 220 if (result) { 221 HandleError(result); 222 return false; 223 } 224 225 // Set the desired number of frames in the IO buffer (output scope). 226 // WARNING: Setting this value changes the frame size for all audio units in 227 // the current process. It's imperative that the input and output frame sizes 228 // be the same as audio_util::GetAudioHardwareBufferSize(). 229 // TODO(henrika): Due to http://crrev.com/159666 this is currently not true 230 // and should be fixed, a CHECK() should be added at that time. 231 result = AudioUnitSetProperty(audio_unit_, 232 kAudioDevicePropertyBufferFrameSize, 233 kAudioUnitScope_Output, 234 1, 235 &number_of_frames_, // size is set in the ctor 236 sizeof(number_of_frames_)); 237 if (result) { 238 HandleError(result); 239 return false; 240 } 241 242 // Finally, initialize the audio unit and ensure that it is ready to render. 243 // Allocates memory according to the maximum number of audio frames 244 // it can produce in response to a single render call. 245 result = AudioUnitInitialize(audio_unit_); 246 if (result) { 247 HandleError(result); 248 return false; 249 } 250 251 // The hardware latency is fixed and will not change during the call. 252 hardware_latency_frames_ = GetHardwareLatency(); 253 254 // The master channel is 0, Left and right are channels 1 and 2. 255 // And the master channel is not counted in |number_of_channels_in_frame_|. 256 number_of_channels_in_frame_ = GetNumberOfChannelsFromStream(); 257 258 return true; 259} 260 261void AUAudioInputStream::Start(AudioInputCallback* callback) { 262 DCHECK(callback); 263 DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully"; 264 if (started_ || !audio_unit_) 265 return; 266 sink_ = callback; 267 OSStatus result = AudioOutputUnitStart(audio_unit_); 268 if (result == noErr) { 269 started_ = true; 270 } 271 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 272 << "Failed to start acquiring data"; 273} 274 275void AUAudioInputStream::Stop() { 276 if (!started_) 277 return; 278 OSStatus result = AudioOutputUnitStop(audio_unit_); 279 if (result == noErr) { 280 started_ = false; 281 } 282 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 283 << "Failed to stop acquiring data"; 284} 285 286void AUAudioInputStream::Close() { 287 // It is valid to call Close() before calling open or Start(). 288 // It is also valid to call Close() after Start() has been called. 289 if (started_) { 290 Stop(); 291 } 292 if (audio_unit_) { 293 // Deallocate the audio unit’s resources. 294 AudioUnitUninitialize(audio_unit_); 295 296 // Terminates our connection to the AUHAL component. 297 CloseComponent(audio_unit_); 298 audio_unit_ = 0; 299 } 300 if (sink_) { 301 sink_->OnClose(this); 302 sink_ = NULL; 303 } 304 305 // Inform the audio manager that we have been closed. This can cause our 306 // destruction. 307 manager_->ReleaseInputStream(this); 308} 309 310double AUAudioInputStream::GetMaxVolume() { 311 // Verify that we have a valid device. 312 if (input_device_id_ == kAudioObjectUnknown) { 313 NOTREACHED() << "Device ID is unknown"; 314 return 0.0; 315 } 316 317 // Query if any of the master, left or right channels has volume control. 318 for (int i = 0; i <= number_of_channels_in_frame_; ++i) { 319 // If the volume is settable, the valid volume range is [0.0, 1.0]. 320 if (IsVolumeSettableOnChannel(i)) 321 return 1.0; 322 } 323 324 // Volume control is not available for the audio stream. 325 return 0.0; 326} 327 328void AUAudioInputStream::SetVolume(double volume) { 329 DVLOG(1) << "SetVolume(volume=" << volume << ")"; 330 DCHECK_GE(volume, 0.0); 331 DCHECK_LE(volume, 1.0); 332 333 // Verify that we have a valid device. 334 if (input_device_id_ == kAudioObjectUnknown) { 335 NOTREACHED() << "Device ID is unknown"; 336 return; 337 } 338 339 Float32 volume_float32 = static_cast<Float32>(volume); 340 AudioObjectPropertyAddress property_address = { 341 kAudioDevicePropertyVolumeScalar, 342 kAudioDevicePropertyScopeInput, 343 kAudioObjectPropertyElementMaster 344 }; 345 346 // Try to set the volume for master volume channel. 347 if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) { 348 OSStatus result = AudioObjectSetPropertyData(input_device_id_, 349 &property_address, 350 0, 351 NULL, 352 sizeof(volume_float32), 353 &volume_float32); 354 if (result != noErr) { 355 DLOG(WARNING) << "Failed to set volume to " << volume_float32; 356 } 357 return; 358 } 359 360 // There is no master volume control, try to set volume for each channel. 361 int successful_channels = 0; 362 for (int i = 1; i <= number_of_channels_in_frame_; ++i) { 363 property_address.mElement = static_cast<UInt32>(i); 364 if (IsVolumeSettableOnChannel(i)) { 365 OSStatus result = AudioObjectSetPropertyData(input_device_id_, 366 &property_address, 367 0, 368 NULL, 369 sizeof(volume_float32), 370 &volume_float32); 371 if (result == noErr) 372 ++successful_channels; 373 } 374 } 375 376 DLOG_IF(WARNING, successful_channels == 0) 377 << "Failed to set volume to " << volume_float32; 378 379 // Update the AGC volume level based on the last setting above. Note that, 380 // the volume-level resolution is not infinite and it is therefore not 381 // possible to assume that the volume provided as input parameter can be 382 // used directly. Instead, a new query to the audio hardware is required. 383 // This method does nothing if AGC is disabled. 384 UpdateAgcVolume(); 385} 386 387double AUAudioInputStream::GetVolume() { 388 // Verify that we have a valid device. 389 if (input_device_id_ == kAudioObjectUnknown){ 390 NOTREACHED() << "Device ID is unknown"; 391 return 0.0; 392 } 393 394 AudioObjectPropertyAddress property_address = { 395 kAudioDevicePropertyVolumeScalar, 396 kAudioDevicePropertyScopeInput, 397 kAudioObjectPropertyElementMaster 398 }; 399 400 if (AudioObjectHasProperty(input_device_id_, &property_address)) { 401 // The device supports master volume control, get the volume from the 402 // master channel. 403 Float32 volume_float32 = 0.0; 404 UInt32 size = sizeof(volume_float32); 405 OSStatus result = AudioObjectGetPropertyData(input_device_id_, 406 &property_address, 407 0, 408 NULL, 409 &size, 410 &volume_float32); 411 if (result == noErr) 412 return static_cast<double>(volume_float32); 413 } else { 414 // There is no master volume control, try to get the average volume of 415 // all the channels. 416 Float32 volume_float32 = 0.0; 417 int successful_channels = 0; 418 for (int i = 1; i <= number_of_channels_in_frame_; ++i) { 419 property_address.mElement = static_cast<UInt32>(i); 420 if (AudioObjectHasProperty(input_device_id_, &property_address)) { 421 Float32 channel_volume = 0; 422 UInt32 size = sizeof(channel_volume); 423 OSStatus result = AudioObjectGetPropertyData(input_device_id_, 424 &property_address, 425 0, 426 NULL, 427 &size, 428 &channel_volume); 429 if (result == noErr) { 430 volume_float32 += channel_volume; 431 ++successful_channels; 432 } 433 } 434 } 435 436 // Get the average volume of the channels. 437 if (successful_channels != 0) 438 return static_cast<double>(volume_float32 / successful_channels); 439 } 440 441 DLOG(WARNING) << "Failed to get volume"; 442 return 0.0; 443} 444 445// AUHAL AudioDeviceOutput unit callback 446OSStatus AUAudioInputStream::InputProc(void* user_data, 447 AudioUnitRenderActionFlags* flags, 448 const AudioTimeStamp* time_stamp, 449 UInt32 bus_number, 450 UInt32 number_of_frames, 451 AudioBufferList* io_data) { 452 // Verify that the correct bus is used (Input bus/Element 1) 453 DCHECK_EQ(bus_number, static_cast<UInt32>(1)); 454 AUAudioInputStream* audio_input = 455 reinterpret_cast<AUAudioInputStream*>(user_data); 456 DCHECK(audio_input); 457 if (!audio_input) 458 return kAudioUnitErr_InvalidElement; 459 460 // Receive audio from the AUHAL from the output scope of the Audio Unit. 461 OSStatus result = AudioUnitRender(audio_input->audio_unit(), 462 flags, 463 time_stamp, 464 bus_number, 465 number_of_frames, 466 audio_input->audio_buffer_list()); 467 if (result) 468 return result; 469 470 // Deliver recorded data to the consumer as a callback. 471 return audio_input->Provide(number_of_frames, 472 audio_input->audio_buffer_list(), 473 time_stamp); 474} 475 476OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames, 477 AudioBufferList* io_data, 478 const AudioTimeStamp* time_stamp) { 479 // Update the capture latency. 480 double capture_latency_frames = GetCaptureLatency(time_stamp); 481 482 // Update the AGC volume level once every second. Note that, |volume| is 483 // also updated each time SetVolume() is called through IPC by the 484 // render-side AGC. 485 double normalized_volume = 0.0; 486 QueryAgcVolume(&normalized_volume); 487 488 AudioBuffer& buffer = io_data->mBuffers[0]; 489 uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData); 490 uint32 capture_delay_bytes = static_cast<uint32> 491 ((capture_latency_frames + 0.5) * format_.mBytesPerFrame); 492 DCHECK(audio_data); 493 if (!audio_data) 494 return kAudioUnitErr_InvalidElement; 495 496 // See http://crbug.com/154352 for details. 497 CHECK_EQ(number_of_frames, static_cast<UInt32>(number_of_frames_)); 498 499 // Accumulate captured audio in FIFO until we can match the output size 500 // requested by the client. 501 DCHECK_LE(fifo_->forward_bytes(), requested_size_bytes_); 502 fifo_->Append(audio_data, buffer.mDataByteSize); 503 504 // Deliver recorded data to the client as soon as the FIFO contains a 505 // sufficient amount. 506 if (fifo_->forward_bytes() >= requested_size_bytes_) { 507 // Read from FIFO into temporary data buffer. 508 fifo_->Read(data_->GetWritableData(), requested_size_bytes_); 509 510 // Deliver data packet, delay estimation and volume level to the user. 511 sink_->OnData(this, 512 data_->GetData(), 513 requested_size_bytes_, 514 capture_delay_bytes, 515 normalized_volume); 516 } 517 518 return noErr; 519} 520 521int AUAudioInputStream::HardwareSampleRate() { 522 // Determine the default input device's sample-rate. 523 AudioDeviceID device_id = kAudioObjectUnknown; 524 UInt32 info_size = sizeof(device_id); 525 526 AudioObjectPropertyAddress default_input_device_address = { 527 kAudioHardwarePropertyDefaultInputDevice, 528 kAudioObjectPropertyScopeGlobal, 529 kAudioObjectPropertyElementMaster 530 }; 531 OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 532 &default_input_device_address, 533 0, 534 0, 535 &info_size, 536 &device_id); 537 OSSTATUS_DCHECK(result == noErr, result); 538 if (result) 539 return 0.0; 540 541 Float64 nominal_sample_rate; 542 info_size = sizeof(nominal_sample_rate); 543 544 AudioObjectPropertyAddress nominal_sample_rate_address = { 545 kAudioDevicePropertyNominalSampleRate, 546 kAudioObjectPropertyScopeGlobal, 547 kAudioObjectPropertyElementMaster 548 }; 549 result = AudioObjectGetPropertyData(device_id, 550 &nominal_sample_rate_address, 551 0, 552 0, 553 &info_size, 554 &nominal_sample_rate); 555 DCHECK_EQ(result, 0); 556 if (result) 557 return 0.0; 558 559 return static_cast<int>(nominal_sample_rate); 560} 561 562double AUAudioInputStream::GetHardwareLatency() { 563 if (!audio_unit_ || input_device_id_ == kAudioObjectUnknown) { 564 DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown"; 565 return 0.0; 566 } 567 568 // Get audio unit latency. 569 Float64 audio_unit_latency_sec = 0.0; 570 UInt32 size = sizeof(audio_unit_latency_sec); 571 OSStatus result = AudioUnitGetProperty(audio_unit_, 572 kAudioUnitProperty_Latency, 573 kAudioUnitScope_Global, 574 0, 575 &audio_unit_latency_sec, 576 &size); 577 OSSTATUS_DLOG_IF(WARNING, result != noErr, result) 578 << "Could not get audio unit latency"; 579 580 // Get input audio device latency. 581 AudioObjectPropertyAddress property_address = { 582 kAudioDevicePropertyLatency, 583 kAudioDevicePropertyScopeInput, 584 kAudioObjectPropertyElementMaster 585 }; 586 UInt32 device_latency_frames = 0; 587 size = sizeof(device_latency_frames); 588 result = AudioObjectGetPropertyData(input_device_id_, 589 &property_address, 590 0, 591 NULL, 592 &size, 593 &device_latency_frames); 594 DLOG_IF(WARNING, result != noErr) << "Could not get audio device latency."; 595 596 return static_cast<double>((audio_unit_latency_sec * 597 format_.mSampleRate) + device_latency_frames); 598} 599 600double AUAudioInputStream::GetCaptureLatency( 601 const AudioTimeStamp* input_time_stamp) { 602 // Get the delay between between the actual recording instant and the time 603 // when the data packet is provided as a callback. 604 UInt64 capture_time_ns = AudioConvertHostTimeToNanos( 605 input_time_stamp->mHostTime); 606 UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); 607 double delay_frames = static_cast<double> 608 (1e-9 * (now_ns - capture_time_ns) * format_.mSampleRate); 609 610 // Total latency is composed by the dynamic latency and the fixed 611 // hardware latency. 612 return (delay_frames + hardware_latency_frames_); 613} 614 615int AUAudioInputStream::GetNumberOfChannelsFromStream() { 616 // Get the stream format, to be able to read the number of channels. 617 AudioObjectPropertyAddress property_address = { 618 kAudioDevicePropertyStreamFormat, 619 kAudioDevicePropertyScopeInput, 620 kAudioObjectPropertyElementMaster 621 }; 622 AudioStreamBasicDescription stream_format; 623 UInt32 size = sizeof(stream_format); 624 OSStatus result = AudioObjectGetPropertyData(input_device_id_, 625 &property_address, 626 0, 627 NULL, 628 &size, 629 &stream_format); 630 if (result != noErr) { 631 DLOG(WARNING) << "Could not get stream format"; 632 return 0; 633 } 634 635 return static_cast<int>(stream_format.mChannelsPerFrame); 636} 637 638void AUAudioInputStream::HandleError(OSStatus err) { 639 NOTREACHED() << "error " << GetMacOSStatusErrorString(err) 640 << " (" << err << ")"; 641 if (sink_) 642 sink_->OnError(this, static_cast<int>(err)); 643} 644 645bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel) { 646 Boolean is_settable = false; 647 AudioObjectPropertyAddress property_address = { 648 kAudioDevicePropertyVolumeScalar, 649 kAudioDevicePropertyScopeInput, 650 static_cast<UInt32>(channel) 651 }; 652 OSStatus result = AudioObjectIsPropertySettable(input_device_id_, 653 &property_address, 654 &is_settable); 655 return (result == noErr) ? is_settable : false; 656} 657 658} // namespace media 659