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