summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorcpu@google.com <cpu@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-13 23:57:32 +0000
committercpu@google.com <cpu@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-01-13 23:57:32 +0000
commit999ebd77596b7e9ee3c13fef014d3b2d6637d559 (patch)
tree7de7fe03640a512272955cf9f3274035ae7be144 /media
parent7cecfb2fa8bd8bb4254f8d83ad0ce5d23961f98c (diff)
downloadchromium_src-999ebd77596b7e9ee3c13fef014d3b2d6637d559.zip
chromium_src-999ebd77596b7e9ee3c13fef014d3b2d6637d559.tar.gz
chromium_src-999ebd77596b7e9ee3c13fef014d3b2d6637d559.tar.bz2
Low level windows audio support (part 2 of 2)
- Implementation using the WaveXXXX windows API - Unit tests for the implementation and for the previous CL that had the sine wave simple source. This is the harder one :) I tried to add comments to illuminate the thinking. Review URL: http://codereview.chromium.org/17403 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@7988 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/win/audio_output_win_unittest.cc217
-rw-r--r--media/audio/win/waveout_output_win.cc255
-rw-r--r--media/audio/win/waveout_output_win.h106
-rw-r--r--media/build/media.vcproj20
4 files changed, 589 insertions, 9 deletions
diff --git a/media/audio/win/audio_output_win_unittest.cc b/media/audio/win/audio_output_win_unittest.cc
index 4d3874d..9b88c0c 100644
--- a/media/audio/win/audio_output_win_unittest.cc
+++ b/media/audio/win/audio_output_win_unittest.cc
@@ -2,28 +2,42 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
#include "media/audio/audio_output.h"
+#include "media/audio/simple_sources.h"
#include "testing/gtest/include/gtest/gtest.h"
-class SimpleSource8bitStereo : public AudioOutputStream::AudioSourceCallback {
+// This class allows to find out if the callbacks are occurring as
+// expected and if any error has been reported.
+class TestSourceBasic : public AudioOutputStream::AudioSourceCallback {
public:
- explicit SimpleSource8bitStereo(size_t bytes_to_write)
- : byte_count_(bytes_to_write), callback_count_(0) {
+ explicit TestSourceBasic()
+ : callback_count_(0),
+ had_error_(0),
+ was_closed_(0) {
}
// AudioSourceCallback::OnMoreData implementation:
virtual size_t OnMoreData(AudioOutputStream* stream,
void* dest, size_t max_size) {
++callback_count_;
- return byte_count_;
+ // Touch the first byte to make sure memory is good.
+ if (max_size)
+ reinterpret_cast<char*>(dest)[0] = 1;
+ return max_size;
}
// AudioSourceCallback::OnClose implementation:
virtual void OnClose(AudioOutputStream* stream) {
+ ++was_closed_;
}
// AudioSourceCallback::OnError implementation:
virtual void OnError(AudioOutputStream* stream, int code) {
+ ++had_error_;
}
// Returns how many times OnMoreData() has been called.
@@ -31,23 +45,208 @@ class SimpleSource8bitStereo : public AudioOutputStream::AudioSourceCallback {
return callback_count_;
}
+ // Returns how many times the OnError callback was called.
+ int had_error() const {
+ return had_error_;
+ }
+
+ void set_error(bool error) {
+ had_error_ += error ? 1 : 0;
+ }
+
+ // Returns how many times the OnClose callback was called.
+ int was_closed() const {
+ return was_closed_;
+ }
+
private:
- size_t byte_count_;
int callback_count_;
+ int had_error_;
+ int was_closed_;
+};
+
+// Specializes TestSourceBasic to detect that the AudioStream is using
+// double buffering correctly.
+class TestSourceDoubleBuffer : public TestSourceBasic {
+ public:
+ TestSourceDoubleBuffer() {
+ buffer_address_[0] = NULL;
+ buffer_address_[1] = NULL;
+ }
+ // Override of TestSourceBasic::OnMoreData.
+ virtual size_t OnMoreData(AudioOutputStream* stream,
+ void* dest, size_t max_size) {
+ // Call the base, which increments the callback_count_.
+ TestSourceBasic::OnMoreData(stream, dest, max_size);
+ if (callback_count() % 2) {
+ set_error(!CompareExistingIfNotNULL(1, dest));
+ } else {
+ set_error(!CompareExistingIfNotNULL(0, dest));
+ }
+ if (callback_count() > 2) {
+ set_error(buffer_address_[0] == buffer_address_[1]);
+ }
+ return max_size;
+ }
+
+ private:
+ bool CompareExistingIfNotNULL(size_t index, void* address) {
+ void*& entry = buffer_address_[index];
+ if (!entry)
+ entry = address;
+ return (entry == address);
+ }
+
+ void* buffer_address_[2];
};
-// Validate that the AudioManager::AUDIO_MOCK works.
-TEST(WinAudioTest, MockStream) {
- AudioManager* audio_man = GetAudioManager();
+
+
+// ============================================================================
+// Validate that the AudioManager::AUDIO_MOCK callbacks work.
+TEST(WinAudioTest, MockStreamBasicCallbacks) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
ASSERT_TRUE(NULL != audio_man);
AudioOutputStream* oas =
audio_man->MakeAudioStream(AudioManager::AUDIO_MOCK, 2, 8000, 8);
ASSERT_TRUE(NULL != oas);
EXPECT_TRUE(oas->Open(256));
- SimpleSource8bitStereo source(256);
+ TestSourceBasic source;
oas->Start(&source);
EXPECT_GT(source.callback_count(), 0);
+ oas->Stop();
oas->Close();
+ EXPECT_EQ(0, source.had_error());
+ EXPECT_EQ(1, source.was_closed());
}
+// Validate that the SineWaveAudioSource writes the expected values for
+// the FORMAT_16BIT_MONO. The values are carefully selected so rounding issues
+// do not affect the result. We also test that AudioManager::GetLastMockBuffer
+// works.
+TEST(WinAudioTest, SineWaveAudio16MonoTest) {
+ const size_t samples = 1024;
+ const size_t bytes_per_sample = 2;
+ const int freq = 200;
+
+ SineWaveAudioSource source(SineWaveAudioSource::FORMAT_16BIT_LINEAR_PCM, 1,
+ freq, AudioManager::kTelephoneSampleRate);
+
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_MOCK, 1,
+ AudioManager::kTelephoneSampleRate,
+ bytes_per_sample * 2);
+ ASSERT_TRUE(NULL != oas);
+ EXPECT_TRUE(oas->Open(samples * bytes_per_sample));
+
+ oas->Start(&source);
+ oas->Stop();
+ oas->Close();
+
+ const int16* last_buffer =
+ reinterpret_cast<const int16*>(audio_man->GetLastMockBuffer());
+ ASSERT_TRUE(NULL != last_buffer);
+
+ size_t half_period = AudioManager::kTelephoneSampleRate / (freq * 2);
+
+ // Spot test positive incursion of sine wave.
+ EXPECT_EQ(0, last_buffer[0]);
+ EXPECT_EQ(5126, last_buffer[1]);
+ EXPECT_TRUE(last_buffer[1] < last_buffer[2]);
+ EXPECT_TRUE(last_buffer[2] < last_buffer[3]);
+ // Spot test negative incursion of sine wave.
+ EXPECT_EQ(0, last_buffer[half_period]);
+ EXPECT_EQ(-5126, last_buffer[half_period + 1]);
+ EXPECT_TRUE(last_buffer[half_period + 1] > last_buffer[half_period + 2]);
+ EXPECT_TRUE(last_buffer[half_period + 2] > last_buffer[half_period + 3]);
+}
+
+// ===========================================================================
+// Validation of AudioManager::AUDIO_PCM_LINEAR
+
+// Test that can it be created and closed.
+TEST(WinAudioTest, PCMWaveStreamGetAndClose) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 2, 8000, 16);
+ ASSERT_TRUE(NULL != oas);
+ oas->Close();
+}
+
+// Test that it can be opened and closed.
+TEST(WinAudioTest, PCMWaveStreamOpenAndClose) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 2, 8000, 16);
+ ASSERT_TRUE(NULL != oas);
+ EXPECT_TRUE(oas->Open(1024));
+ oas->Close();
+}
+
+// Test that it uses the double buffers correctly. Because it uses the actual
+// audio device, you might hear a short pop noise for a short time.
+TEST(WinAudioTest, PCMWaveStreamDoubleBuffer) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1, 16000, 16);
+ ASSERT_TRUE(NULL != oas);
+ TestSourceDoubleBuffer test_double_buffer;
+ EXPECT_TRUE(oas->Open(512));
+ oas->Start(&test_double_buffer);
+ ::Sleep(300);
+ EXPECT_GT(test_double_buffer.callback_count(), 2);
+ EXPECT_FALSE(test_double_buffer.had_error());
+ oas->Stop();
+ ::Sleep(1000);
+ oas->Close();
+}
+
+// This test produces actual audio for 1.5 seconds on the default wave
+// device at 44.1K s/sec. Parameters have been chosen carefully so you should
+// not hear pops or noises while the sound is playing.
+TEST(WinAudioTest, PCMWaveStreamPlay200HzTone44Kss) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1,
+ AudioManager::kAudioCDSampleRate, 16);
+ ASSERT_TRUE(NULL != oas);
+
+ SineWaveAudioSource source(SineWaveAudioSource::FORMAT_16BIT_LINEAR_PCM, 1,
+ 200.0, AudioManager::kAudioCDSampleRate);
+ size_t bytes_100_ms = (AudioManager::kAudioCDSampleRate / 10) * 2;
+
+ EXPECT_TRUE(oas->Open(bytes_100_ms));
+ oas->Start(&source);
+ ::Sleep(1500);
+ oas->Stop();
+ oas->Close();
+}
+
+// This test produces actual audio for for 1.5 seconds on the default wave
+// device at 22K s/sec. Parameters have been chosen carefully so you should
+// not hear pops or noises while the sound is playing.
+TEST(WinAudioTest, PCMWaveStreamPlay200HzTone22Kss) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ ASSERT_TRUE(NULL != audio_man);
+ AudioOutputStream* oas =
+ audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1,
+ AudioManager::kAudioCDSampleRate/2, 16);
+ ASSERT_TRUE(NULL != oas);
+
+ SineWaveAudioSource source(SineWaveAudioSource::FORMAT_16BIT_LINEAR_PCM, 1,
+ 200.0, AudioManager::kAudioCDSampleRate/2);
+ size_t bytes_100_ms = (AudioManager::kAudioCDSampleRate / 20) * 2;
+
+ EXPECT_TRUE(oas->Open(bytes_100_ms));
+ oas->Start(&source);
+ ::Sleep(1500);
+ oas->Stop();
+ oas->Close();
+}
diff --git a/media/audio/win/waveout_output_win.cc b/media/audio/win/waveout_output_win.cc
new file mode 100644
index 0000000..ae0ff54
--- /dev/null
+++ b/media/audio/win/waveout_output_win.cc
@@ -0,0 +1,255 @@
+// 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);
+ }
+}
+
diff --git a/media/audio/win/waveout_output_win.h b/media/audio/win/waveout_output_win.h
new file mode 100644
index 0000000..bdf35b3
--- /dev/null
+++ b/media/audio/win/waveout_output_win.h
@@ -0,0 +1,106 @@
+// 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.
+
+#ifndef MEDIA_AUDIO_WAVEOUT_OUTPUT_WIN_H_
+#define MEDIA_AUDIO_WAVEOUT_OUTPUT_WIN_H_
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include "base/basictypes.h"
+#include "base/scoped_handle_win.h"
+#include "media/audio/audio_output.h"
+
+class AudioManagerWin;
+
+// Implements PCM audio output support for Windows using the WaveXXX API.
+// While not as nice as the DirectSound-based API, it should work in all target
+// operating systems regardless or DirectX version installed. It is known that
+// in some machines WaveXXX based audio is better while in others DirectSound
+// is better.
+//
+// Important: the OnXXXX functions in AudioSourceCallback are called by more
+// than one thread so it is important to have some form of synchronization if
+// you are keeping state in it.
+class PCMWaveOutAudioOutputStream : public AudioOutputStream {
+ public:
+ // The ctor takes all the usual parameters, plus |manager| which is the
+ // the audio manager who is creating this object and |device_id| which
+ // is provided by the operating system.
+ PCMWaveOutAudioOutputStream(AudioManagerWin* manager,
+ int channels, int sampling_rate,
+ char bits_per_sample, UINT device_id);
+ virtual ~PCMWaveOutAudioOutputStream();
+
+ // Implementation of AudioOutputStream.
+ virtual bool Open(size_t packet_size);
+ virtual void Close();
+ virtual void Start(AudioSourceCallback* callback);
+ virtual void Stop();
+ virtual void SetVolume(double left_level, double right_level);
+ virtual void GetVolume(double* left_level, double* right_level);
+
+ // Sends a buffer to the audio driver for playback.
+ void QueueNextPacket(WAVEHDR* buffer);
+
+ private:
+ enum State {
+ PCMA_BRAND_NEW, // Initial state.
+ PCMA_READY, // Device obtained and ready to play.
+ PCMA_PLAYING, // Playing audio.
+ PCMA_STOPPING, // Trying to stop, waiting for callback to finish.
+ PCMA_STOPPED, // Stopped. Device was reset.
+ PCMA_CLOSED // Device has been released.
+ };
+
+ // Windows calls us back to feed more data to the device here. See msdn
+ // documentation for 'waveOutProc' for details about the parameters.
+ static void CALLBACK WaveCallback(HWAVEOUT hwo, UINT msg, DWORD_PTR instance,
+ DWORD_PTR param1, DWORD_PTR param2);
+
+ // If windows reports an error this function handles it and passes it to
+ // the attached AudioSourceCallback::OnError().
+ void HandleError(MMRESULT error);
+ // Allocates and prepares the memory that will be used for playback. Only
+ // two buffers are created.
+ void SetupBuffers(size_t rq_size);
+ // Deallocates the memory allocated in SetupBuffers.
+ void FreeBuffers();
+
+ // Reader beware. Visual C has stronger guarantees on volatile vars than
+ // most people expect. In fact, it has release semantics on write and
+ // acquire semantics on reads. See the msdn documentation.
+ volatile State state_;
+
+ // The audio manager that created this output stream. We notify it when
+ // we close so it can release its own resources.
+ AudioManagerWin* manager_;
+
+ // We use the callback mostly to periodically request more audio data.
+ AudioSourceCallback* callback_;
+
+ // The size in bytes of each audio buffer, we usually have two of these.
+ size_t buffer_size_;
+
+ // The id assigned by the operating system to the selected wave output
+ // hardware device. Usually this is just -1 which means 'default device'.
+ UINT device_id_;
+
+ // Windows native structure to encode the format parameters.
+ WAVEFORMATEX format_;
+
+ // Handle to the instance of the wave device.
+ HWAVEOUT waveout_;
+
+ // Pointer to the first allocated audio buffer. This object owns it.
+ WAVEHDR* buffer_;
+
+ // An event that is signaled when the callback thread is ready to stop.
+ ScopedHandle stopped_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(PCMWaveOutAudioOutputStream);
+};
+
+#endif // MEDIA_AUDIO_WAVEOUT_OUTPUT_WIN_H_
+
diff --git a/media/build/media.vcproj b/media/build/media.vcproj
index b3fb4f6..23b58cfd 100644
--- a/media/build/media.vcproj
+++ b/media/build/media.vcproj
@@ -165,6 +165,10 @@
Name="audio"
>
<File
+ RelativePath="..\audio\win\audio_manager_win.h"
+ >
+ </File>
+ <File
RelativePath="..\audio\audio_output.h"
>
</File>
@@ -172,6 +176,22 @@
RelativePath="..\audio\win\audio_output_win.cc"
>
</File>
+ <File
+ RelativePath="..\audio\simple_sources.h"
+ >
+ </File>
+ <File
+ RelativePath="..\audio\win\simple_sources_win.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\audio\win\waveout_output_win.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\audio\win\waveout_output_win.h"
+ >
+ </File>
</Filter>
</Files>
<Globals>