root/media/audio/win/waveout_output_win.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. BufferSize
  2. GetBuffer
  3. audio_bus_
  4. Open
  5. SetupBuffers
  6. FreeBuffers
  7. Start
  8. Stop
  9. Close
  10. SetVolume
  11. GetVolume
  12. HandleError
  13. QueueNextPacket
  14. BufferCallback

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/audio/win/waveout_output_win.h"

#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

#include "base/atomicops.h"
#include "base/basictypes.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "media/audio/audio_io.h"
#include "media/audio/win/audio_manager_win.h"

namespace media {

// Some general thoughts about the waveOut API which is badly documented :
// - We use CALLBACK_EVENT mode in which XP signals events such as buffer
//   releases.
// - We use RegisterWaitForSingleObject() so one of threads in thread pool
//   automatically calls our callback that feeds more data to Windows.
// - Windows does not provide a way to query if the device is playing or paused
//   thus it forces you to maintain state, which naturally is not exactly
//   synchronized to the actual device state.

// Sixty four MB is the maximum buffer size per AudioOutputStream.
static const uint32 kMaxOpenBufferSize = 1024 * 1024 * 64;

// See Also
// http://www.thx.com/consumer/home-entertainment/home-theater/surround-sound-speaker-set-up/
// http://en.wikipedia.org/wiki/Surround_sound

static const int kMaxChannelsToMask = 8;
static const unsigned int kChannelsToMask[kMaxChannelsToMask + 1] = {
  0,
  // 1 = Mono
  SPEAKER_FRONT_CENTER,
  // 2 = Stereo
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT,
  // 3 = Stereo + Center
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER,
  // 4 = Quad
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT |
  SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
  // 5 = 5.0
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
  SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
  // 6 = 5.1
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT |
  SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
  SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
  // 7 = 6.1
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT |
  SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
  SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
  SPEAKER_BACK_CENTER,
  // 8 = 7.1
  SPEAKER_FRONT_LEFT  | SPEAKER_FRONT_RIGHT |
  SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
  SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
  SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT
  // TODO(fbarchard): Add additional masks for 7.2 and beyond.
};

inline size_t PCMWaveOutAudioOutputStream::BufferSize() const {
  // Round size of buffer up to the nearest 16 bytes.
  return (sizeof(WAVEHDR) + buffer_size_ + 15u) & static_cast<size_t>(~15);
}

inline WAVEHDR* PCMWaveOutAudioOutputStream::GetBuffer(int n) const {
  DCHECK_GE(n, 0);
  DCHECK_LT(n, num_buffers_);
  return reinterpret_cast<WAVEHDR*>(&buffers_[n * BufferSize()]);
}

PCMWaveOutAudioOutputStream::PCMWaveOutAudioOutputStream(
    AudioManagerWin* manager, const AudioParameters& params, int num_buffers,
    UINT device_id)
    : state_(PCMA_BRAND_NEW),
      manager_(manager),
      device_id_(device_id),
      waveout_(NULL),
      callback_(NULL),
      num_buffers_(num_buffers),
      buffer_size_(params.GetBytesPerBuffer()),
      volume_(1),
      channels_(params.channels()),
      pending_bytes_(0),
      waiting_handle_(NULL),
      audio_bus_(AudioBus::Create(params)) {
  format_.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  format_.Format.nChannels = params.channels();
  format_.Format.nSamplesPerSec = params.sample_rate();
  format_.Format.wBitsPerSample = params.bits_per_sample();
  format_.Format.cbSize = sizeof(format_) - sizeof(WAVEFORMATEX);
  // The next are computed from above.
  format_.Format.nBlockAlign = (format_.Format.nChannels *
                                format_.Format.wBitsPerSample) / 8;
  format_.Format.nAvgBytesPerSec = format_.Format.nBlockAlign *
                                   format_.Format.nSamplesPerSec;
  if (params.channels() > kMaxChannelsToMask) {
    format_.dwChannelMask = kChannelsToMask[kMaxChannelsToMask];
  } else {
    format_.dwChannelMask = kChannelsToMask[params.channels()];
  }
  format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
  format_.Samples.wValidBitsPerSample = params.bits_per_sample();
}

PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() {
  DCHECK(NULL == waveout_);
}

bool PCMWaveOutAudioOutputStream::Open() {
  if (state_ != PCMA_BRAND_NEW)
    return false;
  if (BufferSize() * num_buffers_ > kMaxOpenBufferSize)
    return false;
  if (num_buffers_ < 2 || num_buffers_ > 5)
    return false;

  // Create buffer event.
  buffer_event_.Set(::CreateEvent(NULL,    // Security attributes.
                                  FALSE,   // It will auto-reset.
                                  FALSE,   // Initial state.
                                  NULL));  // No name.
  if (!buffer_event_.Get())
    return false;

  // Open the device.
  // We'll be getting buffer_event_ events when it's time to refill the buffer.
  MMRESULT result = ::waveOutOpen(
      &waveout_,
      device_id_,
      reinterpret_cast<LPCWAVEFORMATEX>(&format_),
      reinterpret_cast<DWORD_PTR>(buffer_event_.Get()),
      NULL,
      CALLBACK_EVENT);
  if (result != MMSYSERR_NOERROR)
    return false;

  SetupBuffers();
  state_ = PCMA_READY;
  return true;
}

void PCMWaveOutAudioOutputStream::SetupBuffers() {
  buffers_.reset(new char[BufferSize() * num_buffers_]);
  for (int ix = 0; ix != num_buffers_; ++ix) {
    WAVEHDR* buffer = GetBuffer(ix);
    buffer->lpData = reinterpret_cast<char*>(buffer) + sizeof(WAVEHDR);
    buffer->dwBufferLength = buffer_size_;
    buffer->dwBytesRecorded = 0;
    buffer->dwFlags = WHDR_DONE;
    buffer->dwLoops = 0;
    // Tell windows sound drivers about our buffers. Not documented what
    // this does but we can guess that causes the OS to keep a reference to
    // the memory pages so the driver can use them without worries.
    ::waveOutPrepareHeader(waveout_, buffer, sizeof(WAVEHDR));
  }
}

void PCMWaveOutAudioOutputStream::FreeBuffers() {
  for (int ix = 0; ix != num_buffers_; ++ix) {
    ::waveOutUnprepareHeader(waveout_, GetBuffer(ix), sizeof(WAVEHDR));
  }
  buffers_.reset();
}

// Initially we ask the source to fill up all audio buffers. If we don't do
// this then we would always get the driver callback when it is about to run
// samples and that would leave too little time to react.
void PCMWaveOutAudioOutputStream::Start(AudioSourceCallback* callback) {
  if (state_ != PCMA_READY)
    return;
  callback_ = callback;

  // Reset buffer event, it can be left in the arbitrary state if we
  // previously stopped the stream. Can happen because we are stopping
  // callbacks before stopping playback itself.
  if (!::ResetEvent(buffer_event_.Get())) {
    HandleError(MMSYSERR_ERROR);
    return;
  }

  // Start watching for buffer events.
  if (!::RegisterWaitForSingleObject(&waiting_handle_,
                                     buffer_event_.Get(),
                                     &BufferCallback,
                                     this,
                                     INFINITE,
                                     WT_EXECUTEDEFAULT)) {
    HandleError(MMSYSERR_ERROR);
    waiting_handle_ = NULL;
    return;
  }

  state_ = PCMA_PLAYING;

  // Queue the buffers.
  pending_bytes_ = 0;
  for (int ix = 0; ix != num_buffers_; ++ix) {
    WAVEHDR* buffer = GetBuffer(ix);
    QueueNextPacket(buffer);  // Read more data.
    pending_bytes_ += buffer->dwBufferLength;
  }

  // From now on |pending_bytes_| would be accessed by callback thread.
  // Most likely waveOutPause() or waveOutRestart() has its own memory barrier,
  // but issuing our own is safer.
  base::subtle::MemoryBarrier();

  MMRESULT result = ::waveOutPause(waveout_);
  if (result != MMSYSERR_NOERROR) {
    HandleError(result);
    return;
  }

  // Send the buffers to the audio driver. Note that the device is paused
  // so we avoid entering the callback method while still here.
  for (int ix = 0; ix != num_buffers_; ++ix) {
    result = ::waveOutWrite(waveout_, GetBuffer(ix), sizeof(WAVEHDR));
    if (result != MMSYSERR_NOERROR) {
      HandleError(result);
      break;
    }
  }
  result = ::waveOutRestart(waveout_);
  if (result != MMSYSERR_NOERROR) {
    HandleError(result);
    return;
  }
}

// Stopping is tricky if we want it be fast.
// For now just do it synchronously and avoid all the complexities.
// TODO(enal): if we want faster Stop() we can create singleton that keeps track
//             of all currently playing streams. Then you don't have to wait
//             till all callbacks are completed. Of course access to singleton
//             should be under its own lock, and checking the liveness and
//             acquiring the lock on stream should be done atomically.
void PCMWaveOutAudioOutputStream::Stop() {
  if (state_ != PCMA_PLAYING)
    return;
  state_ = PCMA_STOPPING;
  base::subtle::MemoryBarrier();

  // Stop watching for buffer event, waits until outstanding callbacks finish.
  if (waiting_handle_) {
    if (!::UnregisterWaitEx(waiting_handle_, INVALID_HANDLE_VALUE))
      HandleError(::GetLastError());
    waiting_handle_ = NULL;
  }

  // Stop playback.
  MMRESULT res = ::waveOutReset(waveout_);
  if (res != MMSYSERR_NOERROR)
    HandleError(res);

  // Wait for lock to ensure all outstanding callbacks have completed.
  base::AutoLock auto_lock(lock_);

  // waveOutReset() leaves buffers in the unpredictable state, causing
  // problems if we want to close, release, or reuse them. Fix the states.
  for (int ix = 0; ix != num_buffers_; ++ix)
    GetBuffer(ix)->dwFlags = WHDR_PREPARED;

  // Don't use callback after Stop().
  callback_ = NULL;

  state_ = PCMA_READY;
}

// We can Close in any state except that trying to close a stream that is
// playing Windows generates an error. We cannot propagate it to the source,
// as callback_ is set to NULL. Just print it and hope somebody somehow
// will find it...
void PCMWaveOutAudioOutputStream::Close() {
  // Force Stop() to ensure it's safe to release buffers and free the stream.
  Stop();

  if (waveout_) {
    FreeBuffers();

    // waveOutClose() generates a WIM_CLOSE callback.  In case Start() was never
    // called, force a reset to ensure close succeeds.
    MMRESULT res = ::waveOutReset(waveout_);
    DCHECK_EQ(res, static_cast<MMRESULT>(MMSYSERR_NOERROR));
    res = ::waveOutClose(waveout_);
    DCHECK_EQ(res, static_cast<MMRESULT>(MMSYSERR_NOERROR));
    state_ = PCMA_CLOSED;
    waveout_ = NULL;
  }

  // Tell the audio manager that we have been released. This can result in
  // the manager destroying us in-place so this needs to be the last thing
  // we do on this function.
  manager_->ReleaseOutputStream(this);
}

void PCMWaveOutAudioOutputStream::SetVolume(double volume) {
  if (!waveout_)
    return;
  volume_ = static_cast<float>(volume);
}

void PCMWaveOutAudioOutputStream::GetVolume(double* volume) {
  if (!waveout_)
    return;
  *volume = volume_;
}

void PCMWaveOutAudioOutputStream::HandleError(MMRESULT error) {
  DLOG(WARNING) << "PCMWaveOutAudio error " << error;
  if (callback_)
    callback_->OnError(this);
}

void PCMWaveOutAudioOutputStream::QueueNextPacket(WAVEHDR *buffer) {
  DCHECK_EQ(channels_, format_.Format.nChannels);
  // Call the source which will fill our buffer with pleasant sounds and
  // return to us how many bytes were used.
  // TODO(fbarchard): Handle used 0 by queueing more.

  // TODO(sergeyu): Specify correct hardware delay for AudioBuffersState.
  int frames_filled = callback_->OnMoreData(
      audio_bus_.get(), AudioBuffersState(pending_bytes_, 0));
  uint32 used = frames_filled * audio_bus_->channels() *
      format_.Format.wBitsPerSample / 8;

  if (used <= buffer_size_) {
    // Note: If this ever changes to output raw float the data must be clipped
    // and sanitized since it may come from an untrusted source such as NaCl.
    audio_bus_->Scale(volume_);
    audio_bus_->ToInterleaved(
        frames_filled, format_.Format.wBitsPerSample / 8, buffer->lpData);

    buffer->dwBufferLength = used * format_.Format.nChannels / channels_;
  } else {
    HandleError(0);
    return;
  }
  buffer->dwFlags = WHDR_PREPARED;
}

// One of the threads in our thread pool asynchronously calls this function when
// buffer_event_ is signalled. Search through all the buffers looking for freed
// ones, fills them with data, and "feed" the Windows.
// Note: by searching through all the buffers we guarantee that we fill all the
//       buffers, even when "event loss" happens, i.e. if Windows signals event
//       when it did not flip into unsignaled state from the previous signal.
void NTAPI PCMWaveOutAudioOutputStream::BufferCallback(PVOID lpParameter,
                                                       BOOLEAN timer_fired) {
  TRACE_EVENT0("audio", "PCMWaveOutAudioOutputStream::BufferCallback");

  DCHECK(!timer_fired);
  PCMWaveOutAudioOutputStream* stream =
      reinterpret_cast<PCMWaveOutAudioOutputStream*>(lpParameter);

  // Lock the stream so callbacks do not interfere with each other.
  // Several callbacks can be called simultaneously by different threads in the
  // thread pool if some of the callbacks are slow, or system is very busy and
  // scheduled callbacks are not called on time.
  base::AutoLock auto_lock(stream->lock_);
  if (stream->state_ != PCMA_PLAYING)
    return;

  for (int ix = 0; ix != stream->num_buffers_; ++ix) {
    WAVEHDR* buffer = stream->GetBuffer(ix);
    if (buffer->dwFlags & WHDR_DONE) {
      // Before we queue the next packet, we need to adjust the number of
      // pending bytes since the last write to hardware.
      stream->pending_bytes_ -= buffer->dwBufferLength;
      stream->QueueNextPacket(buffer);

      // QueueNextPacket() can take a long time, especially if several of them
      // were called back-to-back. Check if we are stopping now.
      if (stream->state_ != PCMA_PLAYING)
        return;

      // Time to send the buffer to the audio driver. Since we are reusing
      // the same buffers we can get away without calling waveOutPrepareHeader.
      MMRESULT result = ::waveOutWrite(stream->waveout_,
                                       buffer,
                                       sizeof(WAVEHDR));
      if (result != MMSYSERR_NOERROR)
        stream->HandleError(result);
      stream->pending_bytes_ += buffer->dwBufferLength;
    }
  }
}

}  // namespace media

/* [<][>][^][v][top][bottom][index][help] */