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