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