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