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.
5 #include "media/audio/mac/audio_low_latency_input_mac.h"
7 #include <CoreServices/CoreServices.h>
9 #include "base/basictypes.h"
10 #include "base/logging.h"
11 #include "base/mac/mac_logging.h"
12 #include "base/metrics/sparse_histogram.h"
13 #include "media/audio/mac/audio_manager_mac.h"
14 #include "media/base/audio_bus.h"
15 #include "media/base/data_buffer.h"
19 // Number of blocks of buffers used in the |fifo_|.
20 const int kNumberOfBlocksBufferInFifo
= 2;
22 // Max length of sequence of TooManyFramesToProcessError errors.
23 // The stream will be stopped as soon as this time limit is passed.
24 const int kMaxErrorTimeoutInSeconds
= 1;
26 static std::ostream
& operator<<(std::ostream
& os
,
27 const AudioStreamBasicDescription
& format
) {
28 os
<< "sample rate : " << format
.mSampleRate
<< std::endl
29 << "format ID : " << format
.mFormatID
<< std::endl
30 << "format flags : " << format
.mFormatFlags
<< std::endl
31 << "bytes per packet : " << format
.mBytesPerPacket
<< std::endl
32 << "frames per packet : " << format
.mFramesPerPacket
<< std::endl
33 << "bytes per frame : " << format
.mBytesPerFrame
<< std::endl
34 << "channels per frame: " << format
.mChannelsPerFrame
<< std::endl
35 << "bits per channel : " << format
.mBitsPerChannel
;
39 // See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
40 // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
41 // for more details and background regarding this implementation.
43 AUAudioInputStream::AUAudioInputStream(AudioManagerMac
* manager
,
44 const AudioParameters
& input_params
,
45 AudioDeviceID audio_device_id
)
47 number_of_frames_(input_params
.frames_per_buffer()),
50 input_device_id_(audio_device_id
),
52 hardware_latency_frames_(0),
53 number_of_channels_in_frame_(0),
54 fifo_(input_params
.channels(),
56 kNumberOfBlocksBufferInFifo
) {
59 // Set up the desired (output) format specified by the client.
60 format_
.mSampleRate
= input_params
.sample_rate();
61 format_
.mFormatID
= kAudioFormatLinearPCM
;
62 format_
.mFormatFlags
= kLinearPCMFormatFlagIsPacked
|
63 kLinearPCMFormatFlagIsSignedInteger
;
64 format_
.mBitsPerChannel
= input_params
.bits_per_sample();
65 format_
.mChannelsPerFrame
= input_params
.channels();
66 format_
.mFramesPerPacket
= 1; // uncompressed audio
67 format_
.mBytesPerPacket
= (format_
.mBitsPerChannel
*
68 input_params
.channels()) / 8;
69 format_
.mBytesPerFrame
= format_
.mBytesPerPacket
;
70 format_
.mReserved
= 0;
72 DVLOG(1) << "Desired ouput format: " << format_
;
74 // Derive size (in bytes) of the buffers that we will render to.
75 UInt32 data_byte_size
= number_of_frames_
* format_
.mBytesPerFrame
;
76 DVLOG(1) << "Size of data buffer in bytes : " << data_byte_size
;
78 // Allocate AudioBuffers to be used as storage for the received audio.
79 // The AudioBufferList structure works as a placeholder for the
80 // AudioBuffer structure, which holds a pointer to the actual data buffer.
81 audio_data_buffer_
.reset(new uint8
[data_byte_size
]);
82 audio_buffer_list_
.mNumberBuffers
= 1;
84 AudioBuffer
* audio_buffer
= audio_buffer_list_
.mBuffers
;
85 audio_buffer
->mNumberChannels
= input_params
.channels();
86 audio_buffer
->mDataByteSize
= data_byte_size
;
87 audio_buffer
->mData
= audio_data_buffer_
.get();
90 AUAudioInputStream::~AUAudioInputStream() {}
92 // Obtain and open the AUHAL AudioOutputUnit for recording.
93 bool AUAudioInputStream::Open() {
94 // Verify that we are not already opened.
98 // Verify that we have a valid device.
99 if (input_device_id_
== kAudioObjectUnknown
) {
100 NOTREACHED() << "Device ID is unknown";
104 // Start by obtaining an AudioOuputUnit using an AUHAL component description.
106 // Description for the Audio Unit we want to use (AUHAL in this case).
107 AudioComponentDescription desc
= {
108 kAudioUnitType_Output
,
109 kAudioUnitSubType_HALOutput
,
110 kAudioUnitManufacturer_Apple
,
115 AudioComponent comp
= AudioComponentFindNext(0, &desc
);
118 // Get access to the service provided by the specified Audio Unit.
119 OSStatus result
= AudioComponentInstanceNew(comp
, &audio_unit_
);
125 // Enable IO on the input scope of the Audio Unit.
127 // After creating the AUHAL object, we must enable IO on the input scope
128 // of the Audio Unit to obtain the device input. Input must be explicitly
129 // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1
130 // of the AUHAL. Beacause the AUHAL can be used for both input and output,
131 // we must also disable IO on the output scope.
135 // Enable input on the AUHAL.
136 result
= AudioUnitSetProperty(audio_unit_
,
137 kAudioOutputUnitProperty_EnableIO
,
138 kAudioUnitScope_Input
,
139 1, // input element 1
147 // Disable output on the AUHAL.
149 result
= AudioUnitSetProperty(audio_unit_
,
150 kAudioOutputUnitProperty_EnableIO
,
151 kAudioUnitScope_Output
,
152 0, // output element 0
153 &enableIO
, // disable
160 // Next, set the audio device to be the Audio Unit's current device.
161 // Note that, devices can only be set to the AUHAL after enabling IO.
162 result
= AudioUnitSetProperty(audio_unit_
,
163 kAudioOutputUnitProperty_CurrentDevice
,
164 kAudioUnitScope_Global
,
167 sizeof(input_device_id_
));
173 // Set up the the desired (output) format.
174 // For obtaining input from a device, the device format is always expressed
175 // on the output scope of the AUHAL's Element 1.
176 result
= AudioUnitSetProperty(audio_unit_
,
177 kAudioUnitProperty_StreamFormat
,
178 kAudioUnitScope_Output
,
187 if (!manager_
->MaybeChangeBufferSize(
188 input_device_id_
, audio_unit_
, 1, number_of_frames_
))
191 // Register the input procedure for the AUHAL.
192 // This procedure will be called when the AUHAL has received new data
193 // from the input device.
194 AURenderCallbackStruct callback
;
195 callback
.inputProc
= InputProc
;
196 callback
.inputProcRefCon
= this;
197 result
= AudioUnitSetProperty(audio_unit_
,
198 kAudioOutputUnitProperty_SetInputCallback
,
199 kAudioUnitScope_Global
,
208 // Finally, initialize the audio unit and ensure that it is ready to render.
209 // Allocates memory according to the maximum number of audio frames
210 // it can produce in response to a single render call.
211 result
= AudioUnitInitialize(audio_unit_
);
217 // The hardware latency is fixed and will not change during the call.
218 hardware_latency_frames_
= GetHardwareLatency();
220 // The master channel is 0, Left and right are channels 1 and 2.
221 // And the master channel is not counted in |number_of_channels_in_frame_|.
222 number_of_channels_in_frame_
= GetNumberOfChannelsFromStream();
227 void AUAudioInputStream::Start(AudioInputCallback
* callback
) {
229 DLOG_IF(ERROR
, !audio_unit_
) << "Open() has not been called successfully";
230 if (started_
|| !audio_unit_
)
233 // Check if we should defer Start() for http://crbug.com/160920.
234 if (manager_
->ShouldDeferStreamStart()) {
235 // Use a cancellable closure so that if Stop() is called before Start()
236 // actually runs, we can cancel the pending start.
237 deferred_start_cb_
.Reset(base::Bind(
238 &AUAudioInputStream::Start
, base::Unretained(this), callback
));
239 manager_
->GetTaskRunner()->PostDelayedTask(
241 deferred_start_cb_
.callback(),
242 base::TimeDelta::FromSeconds(
243 AudioManagerMac::kStartDelayInSecsForPowerEvents
));
248 last_success_time_
= base::TimeTicks::Now();
250 OSStatus result
= AudioOutputUnitStart(audio_unit_
);
251 if (result
== noErr
) {
254 OSSTATUS_DLOG_IF(ERROR
, result
!= noErr
, result
)
255 << "Failed to start acquiring data";
258 void AUAudioInputStream::Stop() {
262 OSStatus result
= AudioOutputUnitStop(audio_unit_
);
263 DCHECK_EQ(result
, noErr
);
268 OSSTATUS_DLOG_IF(ERROR
, result
!= noErr
, result
)
269 << "Failed to stop acquiring data";
272 void AUAudioInputStream::Close() {
273 // It is valid to call Close() before calling open or Start().
274 // It is also valid to call Close() after Start() has been called.
279 // Deallocate the audio unit’s resources.
280 OSStatus result
= AudioUnitUninitialize(audio_unit_
);
281 OSSTATUS_DLOG_IF(ERROR
, result
!= noErr
, result
)
282 << "AudioUnitUninitialize() failed.";
284 result
= AudioComponentInstanceDispose(audio_unit_
);
285 OSSTATUS_DLOG_IF(ERROR
, result
!= noErr
, result
)
286 << "AudioComponentInstanceDispose() failed.";
291 // Inform the audio manager that we have been closed. This can cause our
293 manager_
->ReleaseInputStream(this);
296 double AUAudioInputStream::GetMaxVolume() {
297 // Verify that we have a valid device.
298 if (input_device_id_
== kAudioObjectUnknown
) {
299 NOTREACHED() << "Device ID is unknown";
303 // Query if any of the master, left or right channels has volume control.
304 for (int i
= 0; i
<= number_of_channels_in_frame_
; ++i
) {
305 // If the volume is settable, the valid volume range is [0.0, 1.0].
306 if (IsVolumeSettableOnChannel(i
))
310 // Volume control is not available for the audio stream.
314 void AUAudioInputStream::SetVolume(double volume
) {
315 DVLOG(1) << "SetVolume(volume=" << volume
<< ")";
316 DCHECK_GE(volume
, 0.0);
317 DCHECK_LE(volume
, 1.0);
319 // Verify that we have a valid device.
320 if (input_device_id_
== kAudioObjectUnknown
) {
321 NOTREACHED() << "Device ID is unknown";
325 Float32 volume_float32
= static_cast<Float32
>(volume
);
326 AudioObjectPropertyAddress property_address
= {
327 kAudioDevicePropertyVolumeScalar
,
328 kAudioDevicePropertyScopeInput
,
329 kAudioObjectPropertyElementMaster
332 // Try to set the volume for master volume channel.
333 if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster
)) {
334 OSStatus result
= AudioObjectSetPropertyData(input_device_id_
,
338 sizeof(volume_float32
),
340 if (result
!= noErr
) {
341 DLOG(WARNING
) << "Failed to set volume to " << volume_float32
;
346 // There is no master volume control, try to set volume for each channel.
347 int successful_channels
= 0;
348 for (int i
= 1; i
<= number_of_channels_in_frame_
; ++i
) {
349 property_address
.mElement
= static_cast<UInt32
>(i
);
350 if (IsVolumeSettableOnChannel(i
)) {
351 OSStatus result
= AudioObjectSetPropertyData(input_device_id_
,
355 sizeof(volume_float32
),
358 ++successful_channels
;
362 DLOG_IF(WARNING
, successful_channels
== 0)
363 << "Failed to set volume to " << volume_float32
;
365 // Update the AGC volume level based on the last setting above. Note that,
366 // the volume-level resolution is not infinite and it is therefore not
367 // possible to assume that the volume provided as input parameter can be
368 // used directly. Instead, a new query to the audio hardware is required.
369 // This method does nothing if AGC is disabled.
373 double AUAudioInputStream::GetVolume() {
374 // Verify that we have a valid device.
375 if (input_device_id_
== kAudioObjectUnknown
){
376 NOTREACHED() << "Device ID is unknown";
380 AudioObjectPropertyAddress property_address
= {
381 kAudioDevicePropertyVolumeScalar
,
382 kAudioDevicePropertyScopeInput
,
383 kAudioObjectPropertyElementMaster
386 if (AudioObjectHasProperty(input_device_id_
, &property_address
)) {
387 // The device supports master volume control, get the volume from the
389 Float32 volume_float32
= 0.0;
390 UInt32 size
= sizeof(volume_float32
);
391 OSStatus result
= AudioObjectGetPropertyData(input_device_id_
,
398 return static_cast<double>(volume_float32
);
400 // There is no master volume control, try to get the average volume of
402 Float32 volume_float32
= 0.0;
403 int successful_channels
= 0;
404 for (int i
= 1; i
<= number_of_channels_in_frame_
; ++i
) {
405 property_address
.mElement
= static_cast<UInt32
>(i
);
406 if (AudioObjectHasProperty(input_device_id_
, &property_address
)) {
407 Float32 channel_volume
= 0;
408 UInt32 size
= sizeof(channel_volume
);
409 OSStatus result
= AudioObjectGetPropertyData(input_device_id_
,
415 if (result
== noErr
) {
416 volume_float32
+= channel_volume
;
417 ++successful_channels
;
422 // Get the average volume of the channels.
423 if (successful_channels
!= 0)
424 return static_cast<double>(volume_float32
/ successful_channels
);
427 DLOG(WARNING
) << "Failed to get volume";
431 bool AUAudioInputStream::IsMuted() {
432 // Verify that we have a valid device.
433 DCHECK_NE(input_device_id_
, kAudioObjectUnknown
) << "Device ID is unknown";
435 AudioObjectPropertyAddress property_address
= {
436 kAudioDevicePropertyMute
,
437 kAudioDevicePropertyScopeInput
,
438 kAudioObjectPropertyElementMaster
441 if (!AudioObjectHasProperty(input_device_id_
, &property_address
)) {
442 DLOG(ERROR
) << "Device does not support checking master mute state";
447 UInt32 size
= sizeof(muted
);
448 OSStatus result
= AudioObjectGetPropertyData(
449 input_device_id_
, &property_address
, 0, NULL
, &size
, &muted
);
450 DLOG_IF(WARNING
, result
!= noErr
) << "Failed to get mute state";
451 return result
== noErr
&& muted
!= 0;
454 // AUHAL AudioDeviceOutput unit callback
455 OSStatus
AUAudioInputStream::InputProc(void* user_data
,
456 AudioUnitRenderActionFlags
* flags
,
457 const AudioTimeStamp
* time_stamp
,
459 UInt32 number_of_frames
,
460 AudioBufferList
* io_data
) {
461 // Verify that the correct bus is used (Input bus/Element 1)
462 DCHECK_EQ(bus_number
, static_cast<UInt32
>(1));
463 AUAudioInputStream
* audio_input
=
464 reinterpret_cast<AUAudioInputStream
*>(user_data
);
467 return kAudioUnitErr_InvalidElement
;
469 // Update the |mDataByteSize| value in the audio_buffer_list() since
470 // |number_of_frames| can be changed on the fly.
471 // |mDataByteSize| needs to be exactly mapping to |number_of_frames|,
472 // otherwise it will put CoreAudio into bad state and results in
473 // AudioUnitRender() returning -50 for the new created stream.
474 // We have also seen kAudioUnitErr_TooManyFramesToProcess (-10874) and
475 // kAudioUnitErr_CannotDoInCurrentContext (-10863) as error codes.
476 // See crbug/428706 for details.
477 UInt32 new_size
= number_of_frames
* audio_input
->format_
.mBytesPerFrame
;
478 AudioBuffer
* audio_buffer
= audio_input
->audio_buffer_list()->mBuffers
;
479 if (new_size
!= audio_buffer
->mDataByteSize
) {
480 if (new_size
> audio_buffer
->mDataByteSize
) {
481 // This can happen if the device is unpluged during recording. We
482 // allocate enough memory here to avoid depending on how CoreAudio
484 // See See http://www.crbug.com/434681 for one example when we can enter
486 audio_input
->audio_data_buffer_
.reset(new uint8
[new_size
]);
487 audio_buffer
->mData
= audio_input
->audio_data_buffer_
.get();
490 // Update the |mDataByteSize| to match |number_of_frames|.
491 audio_buffer
->mDataByteSize
= new_size
;
494 // Receive audio from the AUHAL from the output scope of the Audio Unit.
495 OSStatus result
= AudioUnitRender(audio_input
->audio_unit(),
500 audio_input
->audio_buffer_list());
502 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.AudioInputCbErrorMac", result
);
503 OSSTATUS_DLOG(ERROR
, result
) << "AudioUnitRender() failed ";
504 if (result
!= kAudioUnitErr_TooManyFramesToProcess
) {
505 audio_input
->HandleError(result
);
507 DCHECK(!audio_input
->last_success_time_
.is_null());
508 // We delay stopping the stream for kAudioUnitErr_TooManyFramesToProcess
509 // since it has been observed that some USB headsets can cause this error
510 // but only for a few initial frames at startup and then then the stream
511 // returns to a stable state again. See b/19524368 for details.
512 // Instead, we measure time since last valid audio frame and call
513 // HandleError() only if a too long error sequence is detected. We do
514 // this to avoid ending up in a non recoverable bad core audio state.
515 base::TimeDelta time_since_last_success
=
516 base::TimeTicks::Now() - audio_input
->last_success_time_
;
517 if ((time_since_last_success
>
518 base::TimeDelta::FromSeconds(kMaxErrorTimeoutInSeconds
))) {
519 DLOG(ERROR
) << "Too long sequence of TooManyFramesToProcess errors!";
520 audio_input
->HandleError(result
);
525 // Update time of successful call to AudioUnitRender().
526 audio_input
->last_success_time_
= base::TimeTicks::Now();
528 // Deliver recorded data to the consumer as a callback.
529 return audio_input
->Provide(number_of_frames
,
530 audio_input
->audio_buffer_list(),
534 OSStatus
AUAudioInputStream::Provide(UInt32 number_of_frames
,
535 AudioBufferList
* io_data
,
536 const AudioTimeStamp
* time_stamp
) {
537 // Update the capture latency.
538 double capture_latency_frames
= GetCaptureLatency(time_stamp
);
540 // The AGC volume level is updated once every second on a separate thread.
541 // Note that, |volume| is also updated each time SetVolume() is called
542 // through IPC by the render-side AGC.
543 double normalized_volume
= 0.0;
544 GetAgcVolume(&normalized_volume
);
546 AudioBuffer
& buffer
= io_data
->mBuffers
[0];
547 uint8
* audio_data
= reinterpret_cast<uint8
*>(buffer
.mData
);
548 uint32 capture_delay_bytes
= static_cast<uint32
>
549 ((capture_latency_frames
+ 0.5) * format_
.mBytesPerFrame
);
552 return kAudioUnitErr_InvalidElement
;
554 // Dynamically increase capacity of the FIFO to handle larger buffers from
555 // CoreAudio. This can happen in combination with Apple Thunderbolt Displays
556 // when the Display Audio is used as capture source and the cable is first
557 // remove and then inserted again.
558 // See http://www.crbug.com/434681 for details.
559 if (static_cast<int>(number_of_frames
) > fifo_
.GetUnfilledFrames()) {
560 // Derive required increase in number of FIFO blocks. The increase is
561 // typically one block.
563 static_cast<int>((number_of_frames
- fifo_
.GetUnfilledFrames()) /
564 number_of_frames_
) + 1;
565 DLOG(WARNING
) << "Increasing FIFO capacity by " << blocks
<< " blocks";
566 fifo_
.IncreaseCapacity(blocks
);
569 // Copy captured (and interleaved) data into FIFO.
570 fifo_
.Push(audio_data
, number_of_frames
, format_
.mBitsPerChannel
/ 8);
572 // Consume and deliver the data when the FIFO has a block of available data.
573 while (fifo_
.available_blocks()) {
574 const AudioBus
* audio_bus
= fifo_
.Consume();
575 DCHECK_EQ(audio_bus
->frames(), static_cast<int>(number_of_frames_
));
577 // Compensate the audio delay caused by the FIFO.
578 capture_delay_bytes
+= fifo_
.GetAvailableFrames() * format_
.mBytesPerFrame
;
579 sink_
->OnData(this, audio_bus
, capture_delay_bytes
, normalized_volume
);
585 int AUAudioInputStream::HardwareSampleRate() {
586 // Determine the default input device's sample-rate.
587 AudioDeviceID device_id
= kAudioObjectUnknown
;
588 UInt32 info_size
= sizeof(device_id
);
590 AudioObjectPropertyAddress default_input_device_address
= {
591 kAudioHardwarePropertyDefaultInputDevice
,
592 kAudioObjectPropertyScopeGlobal
,
593 kAudioObjectPropertyElementMaster
595 OSStatus result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
596 &default_input_device_address
,
604 Float64 nominal_sample_rate
;
605 info_size
= sizeof(nominal_sample_rate
);
607 AudioObjectPropertyAddress nominal_sample_rate_address
= {
608 kAudioDevicePropertyNominalSampleRate
,
609 kAudioObjectPropertyScopeGlobal
,
610 kAudioObjectPropertyElementMaster
612 result
= AudioObjectGetPropertyData(device_id
,
613 &nominal_sample_rate_address
,
617 &nominal_sample_rate
);
621 return static_cast<int>(nominal_sample_rate
);
624 double AUAudioInputStream::GetHardwareLatency() {
625 if (!audio_unit_
|| input_device_id_
== kAudioObjectUnknown
) {
626 DLOG(WARNING
) << "Audio unit object is NULL or device ID is unknown";
630 // Get audio unit latency.
631 Float64 audio_unit_latency_sec
= 0.0;
632 UInt32 size
= sizeof(audio_unit_latency_sec
);
633 OSStatus result
= AudioUnitGetProperty(audio_unit_
,
634 kAudioUnitProperty_Latency
,
635 kAudioUnitScope_Global
,
637 &audio_unit_latency_sec
,
639 OSSTATUS_DLOG_IF(WARNING
, result
!= noErr
, result
)
640 << "Could not get audio unit latency";
642 // Get input audio device latency.
643 AudioObjectPropertyAddress property_address
= {
644 kAudioDevicePropertyLatency
,
645 kAudioDevicePropertyScopeInput
,
646 kAudioObjectPropertyElementMaster
648 UInt32 device_latency_frames
= 0;
649 size
= sizeof(device_latency_frames
);
650 result
= AudioObjectGetPropertyData(input_device_id_
,
655 &device_latency_frames
);
656 DLOG_IF(WARNING
, result
!= noErr
) << "Could not get audio device latency.";
658 return static_cast<double>((audio_unit_latency_sec
*
659 format_
.mSampleRate
) + device_latency_frames
);
662 double AUAudioInputStream::GetCaptureLatency(
663 const AudioTimeStamp
* input_time_stamp
) {
664 // Get the delay between between the actual recording instant and the time
665 // when the data packet is provided as a callback.
666 UInt64 capture_time_ns
= AudioConvertHostTimeToNanos(
667 input_time_stamp
->mHostTime
);
668 UInt64 now_ns
= AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
669 double delay_frames
= static_cast<double>
670 (1e-9 * (now_ns
- capture_time_ns
) * format_
.mSampleRate
);
672 // Total latency is composed by the dynamic latency and the fixed
674 return (delay_frames
+ hardware_latency_frames_
);
677 int AUAudioInputStream::GetNumberOfChannelsFromStream() {
678 // Get the stream format, to be able to read the number of channels.
679 AudioObjectPropertyAddress property_address
= {
680 kAudioDevicePropertyStreamFormat
,
681 kAudioDevicePropertyScopeInput
,
682 kAudioObjectPropertyElementMaster
684 AudioStreamBasicDescription stream_format
;
685 UInt32 size
= sizeof(stream_format
);
686 OSStatus result
= AudioObjectGetPropertyData(input_device_id_
,
692 if (result
!= noErr
) {
693 DLOG(WARNING
) << "Could not get stream format";
697 return static_cast<int>(stream_format
.mChannelsPerFrame
);
700 void AUAudioInputStream::HandleError(OSStatus err
) {
701 NOTREACHED() << "error " << GetMacOSStatusErrorString(err
)
702 << " (" << err
<< ")";
704 sink_
->OnError(this);
707 bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel
) {
708 Boolean is_settable
= false;
709 AudioObjectPropertyAddress property_address
= {
710 kAudioDevicePropertyVolumeScalar
,
711 kAudioDevicePropertyScopeInput
,
712 static_cast<UInt32
>(channel
)
714 OSStatus result
= AudioObjectIsPropertySettable(input_device_id_
,
717 return (result
== noErr
) ? is_settable
: false;