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