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