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