audio_input_mac.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan// Use of this source code is governed by a BSD-style license that can be 3673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan// found in the LICENSE file. 4673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 5673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan#include "media/audio/mac/audio_input_mac.h" 6673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 7673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan#include <CoreServices/CoreServices.h> 8673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 9673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan#include "base/basictypes.h" 10673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan#include "base/logging.h" 11673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan#include "base/mac/mac_logging.h" 12673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan#include "media/audio/audio_manager_base.h" 13673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 14673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 15673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanannamespace media { 16673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 17673f3dbea64b116166dfa668006cdc84224a27c0Sean CallananPCMQueueInAudioInputStream::PCMQueueInAudioInputStream( 18673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan AudioManagerBase* manager, const AudioParameters& params) 19c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan : manager_(manager), 20673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan callback_(NULL), 21673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan audio_queue_(NULL), 22673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan buffer_size_bytes_(0), 23c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan started_(false) { 24d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan // We must have a manager. 25673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan DCHECK(manager_); 26673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan // A frame is one sample across all channels. In interleaved audio the per 27c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan // frame fields identify the set of n |channels|. In uncompressed audio, a 28c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan // packet is always one frame. 29673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan format_.mSampleRate = params.sample_rate(); 30c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan format_.mFormatID = kAudioFormatLinearPCM; 31673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | 32673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan kLinearPCMFormatFlagIsSignedInteger; 33c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan format_.mBitsPerChannel = params.bits_per_sample(); 34673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan format_.mChannelsPerFrame = params.channels(); 35673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan format_.mFramesPerPacket = 1; 36c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan format_.mBytesPerPacket = (params.bits_per_sample() * params.channels()) / 8; 37c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan format_.mBytesPerFrame = format_.mBytesPerPacket; 38c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan format_.mReserved = 0; 39c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan 40673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan buffer_size_bytes_ = params.GetBytesPerBuffer(); 41282c22c6a6b6e54324b0d474b90d918bbfd3a10eSean Callanan} 42282c22c6a6b6e54324b0d474b90d918bbfd3a10eSean Callanan 43282c22c6a6b6e54324b0d474b90d918bbfd3a10eSean CallananPCMQueueInAudioInputStream::~PCMQueueInAudioInputStream() { 44282c22c6a6b6e54324b0d474b90d918bbfd3a10eSean Callanan DCHECK(!callback_); 45282c22c6a6b6e54324b0d474b90d918bbfd3a10eSean Callanan DCHECK(!audio_queue_); 46282c22c6a6b6e54324b0d474b90d918bbfd3a10eSean Callanan} 47c718b9652bb1a7aea5d133123fcc8bc87277002cSean Callanan 48673f3dbea64b116166dfa668006cdc84224a27c0Sean Callananbool PCMQueueInAudioInputStream::Open() { 49d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan OSStatus err = AudioQueueNewInput(&format_, 50d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan &HandleInputBufferStatic, 51d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan this, 52d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan NULL, // Use OS CFRunLoop for |callback| 53d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan kCFRunLoopCommonModes, 54d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan 0, // Reserved 55d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan &audio_queue_); 56d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan if (err != noErr) { 57d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan HandleError(err); 58d83ecab0aeb229504737e34b2f03c07fcb967480Sean Callanan return false; 59673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan } 60673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan return SetupBuffers(); 61673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan} 62673f3dbea64b116166dfa668006cdc84224a27c0Sean Callanan 63673f3dbea64b116166dfa668006cdc84224a27c0Sean Callananvoid PCMQueueInAudioInputStream::Start(AudioInputCallback* callback) { 64 DCHECK(callback); 65 DLOG_IF(ERROR, !audio_queue_) << "Open() has not been called successfully"; 66 if (callback_ || !audio_queue_) 67 return; 68 callback_ = callback; 69 OSStatus err = AudioQueueStart(audio_queue_, NULL); 70 if (err != noErr) { 71 HandleError(err); 72 } else { 73 started_ = true; 74 } 75} 76 77void PCMQueueInAudioInputStream::Stop() { 78 if (!audio_queue_ || !started_) 79 return; 80 81 // We request a synchronous stop, so the next call can take some time. In 82 // the windows implementation we block here as well. 83 OSStatus err = AudioQueueStop(audio_queue_, true); 84 if (err != noErr) 85 HandleError(err); 86 87 started_ = false; 88 callback_ = NULL; 89} 90 91void PCMQueueInAudioInputStream::Close() { 92 Stop(); 93 94 // It is valid to call Close() before calling Open() or Start(), thus 95 // |audio_queue_| and |callback_| might be NULL. 96 if (audio_queue_) { 97 OSStatus err = AudioQueueDispose(audio_queue_, true); 98 audio_queue_ = NULL; 99 if (err != noErr) 100 HandleError(err); 101 } 102 103 manager_->ReleaseInputStream(this); 104 // CARE: This object may now be destroyed. 105} 106 107double PCMQueueInAudioInputStream::GetMaxVolume() { 108 NOTREACHED() << "Only supported for low-latency mode."; 109 return 0.0; 110} 111 112void PCMQueueInAudioInputStream::SetVolume(double volume) { 113 NOTREACHED() << "Only supported for low-latency mode."; 114} 115 116double PCMQueueInAudioInputStream::GetVolume() { 117 NOTREACHED() << "Only supported for low-latency mode."; 118 return 0.0; 119} 120 121void PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) { 122 NOTREACHED() << "Only supported for low-latency mode."; 123} 124 125bool PCMQueueInAudioInputStream::GetAutomaticGainControl() { 126 NOTREACHED() << "Only supported for low-latency mode."; 127 return false; 128} 129 130void PCMQueueInAudioInputStream::HandleError(OSStatus err) { 131 if (callback_) 132 callback_->OnError(this); 133 // This point should never be reached. 134 OSSTATUS_DCHECK(0, err); 135} 136 137bool PCMQueueInAudioInputStream::SetupBuffers() { 138 DCHECK(buffer_size_bytes_); 139 for (int i = 0; i < kNumberBuffers; ++i) { 140 AudioQueueBufferRef buffer; 141 OSStatus err = AudioQueueAllocateBuffer(audio_queue_, 142 buffer_size_bytes_, 143 &buffer); 144 if (err == noErr) 145 err = QueueNextBuffer(buffer); 146 if (err != noErr) { 147 HandleError(err); 148 return false; 149 } 150 // |buffer| will automatically be freed when |audio_queue_| is released. 151 } 152 return true; 153} 154 155OSStatus PCMQueueInAudioInputStream::QueueNextBuffer( 156 AudioQueueBufferRef audio_buffer) { 157 // Only the first 2 params are needed for recording. 158 return AudioQueueEnqueueBuffer(audio_queue_, audio_buffer, 0, NULL); 159} 160 161// static 162void PCMQueueInAudioInputStream::HandleInputBufferStatic( 163 void* data, 164 AudioQueueRef audio_queue, 165 AudioQueueBufferRef audio_buffer, 166 const AudioTimeStamp* start_time, 167 UInt32 num_packets, 168 const AudioStreamPacketDescription* desc) { 169 reinterpret_cast<PCMQueueInAudioInputStream*>(data)-> 170 HandleInputBuffer(audio_queue, audio_buffer, start_time, 171 num_packets, desc); 172} 173 174void PCMQueueInAudioInputStream::HandleInputBuffer( 175 AudioQueueRef audio_queue, 176 AudioQueueBufferRef audio_buffer, 177 const AudioTimeStamp* start_time, 178 UInt32 num_packets, 179 const AudioStreamPacketDescription* packet_desc) { 180 DCHECK_EQ(audio_queue_, audio_queue); 181 DCHECK(audio_buffer->mAudioData); 182 if (!callback_) { 183 // This can happen if Stop() was called without start. 184 DCHECK_EQ(0U, audio_buffer->mAudioDataByteSize); 185 return; 186 } 187 188 if (audio_buffer->mAudioDataByteSize) { 189 // The AudioQueue API may use a large internal buffer and repeatedly call us 190 // back to back once that internal buffer is filled. When this happens the 191 // renderer client does not have enough time to read data back from the 192 // shared memory before the next write comes along. If HandleInputBuffer() 193 // is called too frequently, Sleep() at least 5ms to ensure the shared 194 // memory doesn't get trampled. 195 // TODO(dalecurtis): This is a HACK. Long term the AudioQueue path is going 196 // away in favor of the AudioUnit based AUAudioInputStream(). Tracked by 197 // http://crbug.com/161383. 198 base::TimeDelta elapsed = base::TimeTicks::Now() - last_fill_; 199 const base::TimeDelta kMinDelay = base::TimeDelta::FromMilliseconds(5); 200 if (elapsed < kMinDelay) 201 base::PlatformThread::Sleep(kMinDelay - elapsed); 202 203 callback_->OnData(this, 204 reinterpret_cast<const uint8*>(audio_buffer->mAudioData), 205 audio_buffer->mAudioDataByteSize, 206 audio_buffer->mAudioDataByteSize, 207 0.0); 208 209 last_fill_ = base::TimeTicks::Now(); 210 } 211 // Recycle the buffer. 212 OSStatus err = QueueNextBuffer(audio_buffer); 213 if (err != noErr) { 214 if (err == kAudioQueueErr_EnqueueDuringReset) { 215 // This is the error you get if you try to enqueue a buffer and the 216 // queue has been closed. Not really a problem if indeed the queue 217 // has been closed. 218 // TODO(joth): PCMQueueOutAudioOutputStream uses callback_ to provide an 219 // extra guard for this situation, but it seems to introduce more 220 // complications than it solves (memory barrier issues accessing it from 221 // multiple threads, looses the means to indicate OnClosed to client). 222 // Should determine if we need to do something equivalent here. 223 return; 224 } 225 HandleError(err); 226 } 227} 228 229} // namespace media 230