Home | History | Annotate | Download | only in mac
      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 
     14 
     15 namespace media {
     16 
     17 PCMQueueInAudioInputStream::PCMQueueInAudioInputStream(
     18     AudioManagerBase* manager, const AudioParameters& params)
     19     : manager_(manager),
     20       callback_(NULL),
     21       audio_queue_(NULL),
     22       buffer_size_bytes_(0),
     23       started_(false) {
     24   // We must have a manager.
     25   DCHECK(manager_);
     26   // A frame is one sample across all channels. In interleaved audio the per
     27   // frame fields identify the set of n |channels|. In uncompressed audio, a
     28   // packet is always one frame.
     29   format_.mSampleRate = params.sample_rate();
     30   format_.mFormatID = kAudioFormatLinearPCM;
     31   format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
     32                          kLinearPCMFormatFlagIsSignedInteger;
     33   format_.mBitsPerChannel = params.bits_per_sample();
     34   format_.mChannelsPerFrame = params.channels();
     35   format_.mFramesPerPacket = 1;
     36   format_.mBytesPerPacket = (params.bits_per_sample() * params.channels()) / 8;
     37   format_.mBytesPerFrame = format_.mBytesPerPacket;
     38   format_.mReserved = 0;
     39 
     40   buffer_size_bytes_ = params.GetBytesPerBuffer();
     41 }
     42 
     43 PCMQueueInAudioInputStream::~PCMQueueInAudioInputStream() {
     44   DCHECK(!callback_);
     45   DCHECK(!audio_queue_);
     46 }
     47 
     48 bool PCMQueueInAudioInputStream::Open() {
     49   OSStatus err = AudioQueueNewInput(&format_,
     50                                     &HandleInputBufferStatic,
     51                                     this,
     52                                     NULL,  // Use OS CFRunLoop for |callback|
     53                                     kCFRunLoopCommonModes,
     54                                     0,  // Reserved
     55                                     &audio_queue_);
     56   if (err != noErr) {
     57     HandleError(err);
     58     return false;
     59   }
     60   return SetupBuffers();
     61 }
     62 
     63 void 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 
     77 void 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 }
     89 
     90 void PCMQueueInAudioInputStream::Close() {
     91   // It is valid to call Close() before calling Open() or Start(), thus
     92   // |audio_queue_| and |callback_| might be NULL.
     93   if (audio_queue_) {
     94     OSStatus err = AudioQueueDispose(audio_queue_, true);
     95     audio_queue_ = NULL;
     96     if (err != noErr)
     97       HandleError(err);
     98   }
     99   if (callback_) {
    100     callback_->OnClose(this);
    101     callback_ = NULL;
    102   }
    103   manager_->ReleaseInputStream(this);
    104   // CARE: This object may now be destroyed.
    105 }
    106 
    107 double PCMQueueInAudioInputStream::GetMaxVolume() {
    108   NOTREACHED() << "Only supported for low-latency mode.";
    109   return 0.0;
    110 }
    111 
    112 void PCMQueueInAudioInputStream::SetVolume(double volume) {
    113   NOTREACHED() << "Only supported for low-latency mode.";
    114 }
    115 
    116 double PCMQueueInAudioInputStream::GetVolume() {
    117   NOTREACHED() << "Only supported for low-latency mode.";
    118   return 0.0;
    119 }
    120 
    121 void PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) {
    122   NOTREACHED() << "Only supported for low-latency mode.";
    123 }
    124 
    125 bool PCMQueueInAudioInputStream::GetAutomaticGainControl() {
    126   NOTREACHED() << "Only supported for low-latency mode.";
    127   return false;
    128 }
    129 
    130 void 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 
    137 bool 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 
    155 OSStatus 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
    162 void 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 
    174 void 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