blob: f18b2fa4287dc59c01ed21596f6c6fbb07d28c18 [file] [log] [blame]
// 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/basictypes.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_util.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(NULL);
}
// 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);
// Caller waits for 1st packet to become available, but not for others,
// so we wait for them here.
if (ix != 0)
callback_->WaitTillDataReady();
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.
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;
MemoryBarrier();
// Stop watching for buffer event, wait till all the callbacks are complete.
// Should be done before ::waveOutReset() call to avoid race condition when
// callback that is currently active and already checked that stream is still
// being played calls ::waveOutWrite() after ::waveOutReset() returns, later
// causing ::waveOutClose() to fail with WAVERR_STILLPLAYING.
// TODO(enal): that delays actual stopping of playback. Alternative can be
// to call ::waveOutReset() twice, once before
// ::UnregisterWaitEx() and once after.
if (waiting_handle_) {
if (!::UnregisterWaitEx(waiting_handle_, INVALID_HANDLE_VALUE)) {
state_ = PCMA_PLAYING;
HandleError(MMSYSERR_ERROR);
return;
}
waiting_handle_ = NULL;
}
// Stop playback.
MMRESULT res = ::waveOutReset(waveout_);
if (res != MMSYSERR_NOERROR) {
state_ = PCMA_PLAYING;
HandleError(res);
return;
}
// 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() {
Stop(); // Just to be sure. No-op if not playing.
if (waveout_) {
MMRESULT result = ::waveOutClose(waveout_);
// If ::waveOutClose() fails we cannot just delete the stream, callback
// may try to access it and would crash. Better to leak the stream.
if (result != MMSYSERR_NOERROR) {
HandleError(result);
state_ = PCMA_PLAYING;
return;
}
state_ = PCMA_CLOSED;
waveout_ = NULL;
FreeBuffers();
}
// 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, error);
}
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.
// HACK: Yield if Read() is called too often. On older platforms which are
// still using the WaveOut backend, we run into synchronization issues where
// the renderer has not finished filling the shared memory when Read() is
// called. Reading too early will lead to clicks and pops. See issues:
// http://crbug.com/161307 and http://crbug.com/61022
callback_->WaitTillDataReady();
// 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_->ToInterleaved(
frames_filled, format_.Format.wBitsPerSample / 8, buffer->lpData);
buffer->dwBufferLength = used * format_.Format.nChannels / channels_;
media::AdjustVolume(buffer->lpData, used,
format_.Format.nChannels,
format_.Format.wBitsPerSample >> 3,
volume_);
} 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