audio_input_mac.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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 deferred_start_cb_.Reset(base::Bind( 73 &PCMQueueInAudioInputStream::Start, base::Unretained(this), callback)); 74 manager_->GetTaskRunner()->PostDelayedTask( 75 FROM_HERE, 76 deferred_start_cb_.callback(), 77 base::TimeDelta::FromSeconds( 78 AudioManagerMac::kStartDelayInSecsForPowerEvents)); 79 return; 80 } 81 82 callback_ = callback; 83 OSStatus err = AudioQueueStart(audio_queue_, NULL); 84 if (err != noErr) { 85 HandleError(err); 86 } else { 87 started_ = true; 88 } 89} 90 91void PCMQueueInAudioInputStream::Stop() { 92 deferred_start_cb_.Cancel(); 93 if (!audio_queue_ || !started_) 94 return; 95 96 // We request a synchronous stop, so the next call can take some time. In 97 // the windows implementation we block here as well. 98 OSStatus err = AudioQueueStop(audio_queue_, true); 99 if (err != noErr) 100 HandleError(err); 101 102 started_ = false; 103 callback_ = NULL; 104} 105 106void PCMQueueInAudioInputStream::Close() { 107 Stop(); 108 109 // It is valid to call Close() before calling Open() or Start(), thus 110 // |audio_queue_| and |callback_| might be NULL. 111 if (audio_queue_) { 112 OSStatus err = AudioQueueDispose(audio_queue_, true); 113 audio_queue_ = NULL; 114 if (err != noErr) 115 HandleError(err); 116 } 117 118 manager_->ReleaseInputStream(this); 119 // CARE: This object may now be destroyed. 120} 121 122double PCMQueueInAudioInputStream::GetMaxVolume() { 123 NOTREACHED() << "Only supported for low-latency mode."; 124 return 0.0; 125} 126 127void PCMQueueInAudioInputStream::SetVolume(double volume) { 128 NOTREACHED() << "Only supported for low-latency mode."; 129} 130 131double PCMQueueInAudioInputStream::GetVolume() { 132 NOTREACHED() << "Only supported for low-latency mode."; 133 return 0.0; 134} 135 136void PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) { 137 NOTREACHED() << "Only supported for low-latency mode."; 138} 139 140bool PCMQueueInAudioInputStream::GetAutomaticGainControl() { 141 NOTREACHED() << "Only supported for low-latency mode."; 142 return false; 143} 144 145void PCMQueueInAudioInputStream::HandleError(OSStatus err) { 146 if (callback_) 147 callback_->OnError(this); 148 // This point should never be reached. 149 OSSTATUS_DCHECK(0, err); 150} 151 152bool PCMQueueInAudioInputStream::SetupBuffers() { 153 DCHECK(buffer_size_bytes_); 154 for (int i = 0; i < kNumberBuffers; ++i) { 155 AudioQueueBufferRef buffer; 156 OSStatus err = AudioQueueAllocateBuffer(audio_queue_, 157 buffer_size_bytes_, 158 &buffer); 159 if (err == noErr) 160 err = QueueNextBuffer(buffer); 161 if (err != noErr) { 162 HandleError(err); 163 return false; 164 } 165 // |buffer| will automatically be freed when |audio_queue_| is released. 166 } 167 return true; 168} 169 170OSStatus PCMQueueInAudioInputStream::QueueNextBuffer( 171 AudioQueueBufferRef audio_buffer) { 172 // Only the first 2 params are needed for recording. 173 return AudioQueueEnqueueBuffer(audio_queue_, audio_buffer, 0, NULL); 174} 175 176// static 177void PCMQueueInAudioInputStream::HandleInputBufferStatic( 178 void* data, 179 AudioQueueRef audio_queue, 180 AudioQueueBufferRef audio_buffer, 181 const AudioTimeStamp* start_time, 182 UInt32 num_packets, 183 const AudioStreamPacketDescription* desc) { 184 reinterpret_cast<PCMQueueInAudioInputStream*>(data)-> 185 HandleInputBuffer(audio_queue, audio_buffer, start_time, 186 num_packets, desc); 187} 188 189void PCMQueueInAudioInputStream::HandleInputBuffer( 190 AudioQueueRef audio_queue, 191 AudioQueueBufferRef audio_buffer, 192 const AudioTimeStamp* start_time, 193 UInt32 num_packets, 194 const AudioStreamPacketDescription* packet_desc) { 195 DCHECK_EQ(audio_queue_, audio_queue); 196 DCHECK(audio_buffer->mAudioData); 197 if (!callback_) { 198 // This can happen if Stop() was called without start. 199 DCHECK_EQ(0U, audio_buffer->mAudioDataByteSize); 200 return; 201 } 202 203 if (audio_buffer->mAudioDataByteSize) { 204 // The AudioQueue API may use a large internal buffer and repeatedly call us 205 // back to back once that internal buffer is filled. When this happens the 206 // renderer client does not have enough time to read data back from the 207 // shared memory before the next write comes along. If HandleInputBuffer() 208 // is called too frequently, Sleep() at least 5ms to ensure the shared 209 // memory doesn't get trampled. 210 // TODO(dalecurtis): This is a HACK. Long term the AudioQueue path is going 211 // away in favor of the AudioUnit based AUAudioInputStream(). Tracked by 212 // http://crbug.com/161383. 213 base::TimeDelta elapsed = base::TimeTicks::Now() - last_fill_; 214 const base::TimeDelta kMinDelay = base::TimeDelta::FromMilliseconds(5); 215 if (elapsed < kMinDelay) 216 base::PlatformThread::Sleep(kMinDelay - elapsed); 217 218 callback_->OnData(this, 219 reinterpret_cast<const uint8*>(audio_buffer->mAudioData), 220 audio_buffer->mAudioDataByteSize, 221 audio_buffer->mAudioDataByteSize, 222 0.0); 223 224 last_fill_ = base::TimeTicks::Now(); 225 } 226 // Recycle the buffer. 227 OSStatus err = QueueNextBuffer(audio_buffer); 228 if (err != noErr) { 229 if (err == kAudioQueueErr_EnqueueDuringReset) { 230 // This is the error you get if you try to enqueue a buffer and the 231 // queue has been closed. Not really a problem if indeed the queue 232 // has been closed. 233 // TODO(joth): PCMQueueOutAudioOutputStream uses callback_ to provide an 234 // extra guard for this situation, but it seems to introduce more 235 // complications than it solves (memory barrier issues accessing it from 236 // multiple threads, looses the means to indicate OnClosed to client). 237 // Should determine if we need to do something equivalent here. 238 return; 239 } 240 HandleError(err); 241 } 242} 243 244} // namespace media 245