audio_input_mac.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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_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
14namespace media {
15
16PCMQueueInAudioInputStream::PCMQueueInAudioInputStream(
17    AudioManagerMac* manager, const AudioParameters& params)
18    : manager_(manager),
19      callback_(NULL),
20      audio_queue_(NULL),
21      buffer_size_bytes_(0),
22      started_(false) {
23  // We must have a manager.
24  DCHECK(manager_);
25  // A frame is one sample across all channels. In interleaved audio the per
26  // frame fields identify the set of n |channels|. In uncompressed audio, a
27  // packet is always one frame.
28  format_.mSampleRate = params.sample_rate();
29  format_.mFormatID = kAudioFormatLinearPCM;
30  format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
31                         kLinearPCMFormatFlagIsSignedInteger;
32  format_.mBitsPerChannel = params.bits_per_sample();
33  format_.mChannelsPerFrame = params.channels();
34  format_.mFramesPerPacket = 1;
35  format_.mBytesPerPacket = (params.bits_per_sample() * params.channels()) / 8;
36  format_.mBytesPerFrame = format_.mBytesPerPacket;
37  format_.mReserved = 0;
38
39  buffer_size_bytes_ = params.GetBytesPerBuffer();
40}
41
42PCMQueueInAudioInputStream::~PCMQueueInAudioInputStream() {
43  DCHECK(!callback_);
44  DCHECK(!audio_queue_);
45}
46
47bool PCMQueueInAudioInputStream::Open() {
48  OSStatus err = AudioQueueNewInput(&format_,
49                                    &HandleInputBufferStatic,
50                                    this,
51                                    NULL,  // Use OS CFRunLoop for |callback|
52                                    kCFRunLoopCommonModes,
53                                    0,  // Reserved
54                                    &audio_queue_);
55  if (err != noErr) {
56    HandleError(err);
57    return false;
58  }
59  return SetupBuffers();
60}
61
62void PCMQueueInAudioInputStream::Start(AudioInputCallback* callback) {
63  DCHECK(callback);
64  DLOG_IF(ERROR, !audio_queue_) << "Open() has not been called successfully";
65  if (callback_ || !audio_queue_)
66    return;
67
68  // Check if we should defer Start() for http://crbug.com/160920.
69  if (manager_->ShouldDeferStreamStart()) {
70    // Use a cancellable closure so that if Stop() is called before Start()
71    // actually runs, we can cancel the pending start.
72    DCHECK(deferred_start_cb_.IsCancelled());
73    deferred_start_cb_.Reset(base::Bind(
74        &PCMQueueInAudioInputStream::Start, base::Unretained(this), callback));
75    manager_->GetTaskRunner()->PostDelayedTask(
76        FROM_HERE,
77        deferred_start_cb_.callback(),
78        base::TimeDelta::FromSeconds(
79            AudioManagerMac::kStartDelayInSecsForPowerEvents));
80    return;
81  }
82
83  callback_ = callback;
84  OSStatus err = AudioQueueStart(audio_queue_, NULL);
85  if (err != noErr) {
86    HandleError(err);
87  } else {
88    started_ = true;
89  }
90}
91
92void PCMQueueInAudioInputStream::Stop() {
93  deferred_start_cb_.Cancel();
94  if (!audio_queue_ || !started_)
95    return;
96
97  // We request a synchronous stop, so the next call can take some time. In
98  // the windows implementation we block here as well.
99  OSStatus err = AudioQueueStop(audio_queue_, true);
100  if (err != noErr)
101    HandleError(err);
102
103  started_ = false;
104  callback_ = NULL;
105}
106
107void PCMQueueInAudioInputStream::Close() {
108  Stop();
109
110  // It is valid to call Close() before calling Open() or Start(), thus
111  // |audio_queue_| and |callback_| might be NULL.
112  if (audio_queue_) {
113    OSStatus err = AudioQueueDispose(audio_queue_, true);
114    audio_queue_ = NULL;
115    if (err != noErr)
116      HandleError(err);
117  }
118
119  manager_->ReleaseInputStream(this);
120  // CARE: This object may now be destroyed.
121}
122
123double PCMQueueInAudioInputStream::GetMaxVolume() {
124  NOTREACHED() << "Only supported for low-latency mode.";
125  return 0.0;
126}
127
128void PCMQueueInAudioInputStream::SetVolume(double volume) {
129  NOTREACHED() << "Only supported for low-latency mode.";
130}
131
132double PCMQueueInAudioInputStream::GetVolume() {
133  NOTREACHED() << "Only supported for low-latency mode.";
134  return 0.0;
135}
136
137void PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) {
138  NOTREACHED() << "Only supported for low-latency mode.";
139}
140
141bool PCMQueueInAudioInputStream::GetAutomaticGainControl() {
142  NOTREACHED() << "Only supported for low-latency mode.";
143  return false;
144}
145
146void PCMQueueInAudioInputStream::HandleError(OSStatus err) {
147  if (callback_)
148    callback_->OnError(this);
149  // This point should never be reached.
150  OSSTATUS_DCHECK(0, err);
151}
152
153bool PCMQueueInAudioInputStream::SetupBuffers() {
154  DCHECK(buffer_size_bytes_);
155  for (int i = 0; i < kNumberBuffers; ++i) {
156    AudioQueueBufferRef buffer;
157    OSStatus err = AudioQueueAllocateBuffer(audio_queue_,
158                                            buffer_size_bytes_,
159                                            &buffer);
160    if (err == noErr)
161      err = QueueNextBuffer(buffer);
162    if (err != noErr) {
163      HandleError(err);
164      return false;
165    }
166    // |buffer| will automatically be freed when |audio_queue_| is released.
167  }
168  return true;
169}
170
171OSStatus PCMQueueInAudioInputStream::QueueNextBuffer(
172    AudioQueueBufferRef audio_buffer) {
173  // Only the first 2 params are needed for recording.
174  return AudioQueueEnqueueBuffer(audio_queue_, audio_buffer, 0, NULL);
175}
176
177// static
178void PCMQueueInAudioInputStream::HandleInputBufferStatic(
179    void* data,
180    AudioQueueRef audio_queue,
181    AudioQueueBufferRef audio_buffer,
182    const AudioTimeStamp* start_time,
183    UInt32 num_packets,
184    const AudioStreamPacketDescription* desc) {
185  reinterpret_cast<PCMQueueInAudioInputStream*>(data)->
186      HandleInputBuffer(audio_queue, audio_buffer, start_time,
187                        num_packets, desc);
188}
189
190void PCMQueueInAudioInputStream::HandleInputBuffer(
191    AudioQueueRef audio_queue,
192    AudioQueueBufferRef audio_buffer,
193    const AudioTimeStamp* start_time,
194    UInt32 num_packets,
195    const AudioStreamPacketDescription* packet_desc) {
196  DCHECK_EQ(audio_queue_, audio_queue);
197  DCHECK(audio_buffer->mAudioData);
198  if (!callback_) {
199    // This can happen if Stop() was called without start.
200    DCHECK_EQ(0U, audio_buffer->mAudioDataByteSize);
201    return;
202  }
203
204  if (audio_buffer->mAudioDataByteSize) {
205    // The AudioQueue API may use a large internal buffer and repeatedly call us
206    // back to back once that internal buffer is filled.  When this happens the
207    // renderer client does not have enough time to read data back from the
208    // shared memory before the next write comes along.  If HandleInputBuffer()
209    // is called too frequently, Sleep() at least 5ms to ensure the shared
210    // memory doesn't get trampled.
211    // TODO(dalecurtis): This is a HACK.  Long term the AudioQueue path is going
212    // away in favor of the AudioUnit based AUAudioInputStream().  Tracked by
213    // http://crbug.com/161383.
214    base::TimeDelta elapsed = base::TimeTicks::Now() - last_fill_;
215    const base::TimeDelta kMinDelay = base::TimeDelta::FromMilliseconds(5);
216    if (elapsed < kMinDelay)
217      base::PlatformThread::Sleep(kMinDelay - elapsed);
218
219    callback_->OnData(this,
220                      reinterpret_cast<const uint8*>(audio_buffer->mAudioData),
221                      audio_buffer->mAudioDataByteSize,
222                      audio_buffer->mAudioDataByteSize,
223                      0.0);
224
225    last_fill_ = base::TimeTicks::Now();
226  }
227  // Recycle the buffer.
228  OSStatus err = QueueNextBuffer(audio_buffer);
229  if (err != noErr) {
230    if (err == kAudioQueueErr_EnqueueDuringReset) {
231      // This is the error you get if you try to enqueue a buffer and the
232      // queue has been closed. Not really a problem if indeed the queue
233      // has been closed.
234      // TODO(joth): PCMQueueOutAudioOutputStream uses callback_ to provide an
235      // extra guard for this situation, but it seems to introduce more
236      // complications than it solves (memory barrier issues accessing it from
237      // multiple threads, looses the means to indicate OnClosed to client).
238      // Should determine if we need to do something equivalent here.
239      return;
240    }
241    HandleError(err);
242  }
243}
244
245}  // namespace media
246