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