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