blob: fa848845c938581454bc5903bfd15583386eca32 [file] [log] [blame]
// Copyright (c) 2006-2008 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 <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
#include "media/audio/win/waveout_output_win.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "media/audio/audio_output.h"
#include "media/audio/win/audio_manager_win.h"
// Some general thoughts about the waveOut API which is badly documented :
// - We use CALLBACK_FUNCTION mode in which XP secretly creates two threads
// named _MixerCallbackThread and _waveThread which have real-time priority.
// The callbacks occur in _waveThread.
// - 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.
// - Some functions, like waveOutReset cannot be called in the callback thread
// or called in any random state because they deadlock. This results in a
// non- instantaneous Stop() method. waveOutPrepareHeader seems to be in the
// same boat.
// - waveOutReset() will forcefully kill the _waveThread so it is important
// to make sure we are not executing inside the audio source's OnMoreData()
// or that we take locks inside WaveCallback() or QueueNextPacket().
namespace {
// We settled for a double buffering scheme. It seems to strike a good balance
// between how fast data needs to be provided versus memory usage.
const size_t kNumBuffers = 2;
// Our sound buffers are allocated once and kept in a linked list using the
// the WAVEHDR::dwUser variable. The last buffer points to the first buffer.
WAVEHDR* GetNextBuffer(WAVEHDR* current) {
return reinterpret_cast<WAVEHDR*>(current->dwUser);
}
} // namespace
PCMWaveOutAudioOutputStream::PCMWaveOutAudioOutputStream(
AudioManagerWin* manager, int channels, int sampling_rate,
char bits_per_sample, UINT device_id)
: state_(PCMA_BRAND_NEW),
manager_(manager),
device_id_(device_id),
waveout_(NULL),
callback_(NULL),
buffer_(NULL),
buffer_size_(0) {
format_.wFormatTag = WAVE_FORMAT_PCM;
format_.nChannels = channels;
format_.nSamplesPerSec = sampling_rate;
format_.wBitsPerSample = bits_per_sample;
format_.cbSize = 0;
// The next are computed from above.
format_.nBlockAlign = (format_.nChannels * format_.wBitsPerSample) / 8;
format_.nAvgBytesPerSec = format_.nBlockAlign * format_.nSamplesPerSec;
// The event is auto-reset.
stopped_event_.Set(::CreateEventW(NULL, FALSE, FALSE, NULL));
}
PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() {
DCHECK(NULL == waveout_);
}
bool PCMWaveOutAudioOutputStream::Open(size_t buffer_size) {
if (state_ != PCMA_BRAND_NEW)
return false;
// Open the device. We'll be getting callback in WaveCallback function. They
// occur in a magic, time-critical thread that windows creates.
MMRESULT result = ::waveOutOpen(&waveout_, device_id_, &format_,
reinterpret_cast<DWORD_PTR>(WaveCallback),
reinterpret_cast<DWORD_PTR>(this),
CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR)
return false;
// If we don't have a packet size we use 100ms.
if (!buffer_size)
buffer_size = format_.nAvgBytesPerSec / 10;
SetupBuffers(buffer_size);
buffer_size_ = buffer_size;
state_ = PCMA_READY;
return true;
}
void PCMWaveOutAudioOutputStream::SetupBuffers(size_t rq_size) {
WAVEHDR* last = NULL;
WAVEHDR* first = NULL;
for (int ix = 0; ix != kNumBuffers; ++ix) {
size_t sz = sizeof(WAVEHDR) + rq_size;
buffer_ = reinterpret_cast<WAVEHDR*>(new char[sz]);
buffer_->lpData = reinterpret_cast<char*>(buffer_) + sizeof(WAVEHDR);
buffer_->dwBufferLength = rq_size;
buffer_->dwBytesRecorded = 0;
buffer_->dwUser = reinterpret_cast<DWORD_PTR>(last);
buffer_->dwFlags = WHDR_DONE;
buffer_->dwLoops = 0;
if (ix == 0)
first = buffer_;
last = buffer_;
// 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));
}
// Fix the first buffer to point to the last one.
first->dwUser = reinterpret_cast<DWORD_PTR>(last);
}
void PCMWaveOutAudioOutputStream::FreeBuffers() {
WAVEHDR* current = buffer_;
for (int ix = 0; ix != kNumBuffers; ++ix) {
WAVEHDR* next = GetNextBuffer(current);
::waveOutUnprepareHeader(waveout_, current, sizeof(WAVEHDR));
delete[] reinterpret_cast<char*>(current);
current = next;
}
buffer_ = NULL;
}
// Initially we ask the source to fill up both 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;
state_ = PCMA_PLAYING;
WAVEHDR* buffer = buffer_;
for (int ix = 0; ix != kNumBuffers; ++ix) {
QueueNextPacket(buffer);
buffer = GetNextBuffer(buffer);
}
}
// Stopping is tricky. First, no buffer should be locked by the audio driver
// or else the waveOutReset will deadlock and secondly, the callback should not
// be inside the AudioSource's OnMoreData because waveOutReset() forcefully
// kills the callback thread.
void PCMWaveOutAudioOutputStream::Stop() {
if (state_ != PCMA_PLAYING)
return;
state_ = PCMA_STOPPING;
// Wait for the callback to finish, it will signal us when ready to be reset.
if (WAIT_OBJECT_0 != ::WaitForSingleObject(stopped_event_, INFINITE)) {
HandleError(::GetLastError());
return;
}
state_ = PCMA_STOPPED;
MMRESULT res = ::waveOutReset(waveout_);
if (res != MMSYSERR_NOERROR) {
state_ = PCMA_PLAYING;
HandleError(res);
}
}
// We can Close in any state except that trying to close a stream that is
// playing Windows generates an error, which we propagate to the source.
void PCMWaveOutAudioOutputStream::Close() {
if (waveout_) {
// waveOutClose generates a callback with WOM_CLOSE id in the same thread.
MMRESULT res = ::waveOutClose(waveout_);
if (res != MMSYSERR_NOERROR) {
HandleError(res);
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_->ReleaseStream(this);
}
// TODO(cpu): Implement SetVolume and GetVolume.
void PCMWaveOutAudioOutputStream::SetVolume(double left_level,
double right_level) {
if (state_ != PCMA_READY)
return;
}
void PCMWaveOutAudioOutputStream::GetVolume(double* left_level,
double* right_level) {
if (state_ != PCMA_READY)
return;
}
void PCMWaveOutAudioOutputStream::HandleError(MMRESULT error) {
DLOG(WARNING) << "PCMWaveOutAudio error " << error;
callback_->OnError(this, error);
}
void PCMWaveOutAudioOutputStream::QueueNextPacket(WAVEHDR *buffer) {
// Call the source which will fill our buffer with pleasant sounds and
// return to us how many bytes were used.
size_t used = callback_->OnMoreData(this, buffer->lpData, buffer_size_);
if (used <= buffer_size_) {
buffer->dwBufferLength = used;
} else {
HandleError(0);
return;
}
// Time to queue the buffer to the audio driver. Since we are reusing
// the same buffers we can get away without calling waveOutPrepareHeader.
buffer->dwFlags = WHDR_PREPARED;
MMRESULT result = ::waveOutWrite(waveout_, buffer, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR)
HandleError(result);
}
// Windows call us back in this function when some events happen. Most notably
// when it is done playing a buffer. Since we use double buffering it is
// convenient to think of |buffer| as free and GetNextBuffer(buffer) as in
// use by the driver.
void PCMWaveOutAudioOutputStream::WaveCallback(HWAVEOUT hwo, UINT msg,
DWORD_PTR instance,
DWORD_PTR param1, DWORD_PTR) {
PCMWaveOutAudioOutputStream* obj =
reinterpret_cast<PCMWaveOutAudioOutputStream*>(instance);
if (msg == WOM_DONE) {
// WOM_DONE indicates that the driver is done with our buffer, we can
// either ask the source for more data or check if we need to stop playing.
WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1);
buffer->dwFlags = WHDR_DONE;
if (obj->state_ == PCMA_STOPPING) {
// The main thread has called Stop() and is waiting to issue waveOutReset
// which will kill this thread. We should not enter AudioSourceCallback
// code anymore.
::SetEvent(obj->stopped_event_);
return;
} else if (obj->state_ == PCMA_STOPPED) {
// Not sure if ever hit this but just in case.
return;
}
obj->QueueNextPacket(buffer);
} else if (msg == WOM_CLOSE) {
// We can be closed before calling Start, so it is possible to have a
// null callback at this point.
if (obj->callback_)
obj->callback_->OnClose(obj);
}
}